File Inclusion: LFI, RFI, and the Road to RCE
LFI Null Byte and Filter Bypass
Defeat an appended extension on a local file inclusion: null-byte truncation on legacy PHP, wrappers that ignore the suffix, and path padding tricks.
Published
An include that appends a fixed extension to your input is the most common half-defense on an LFI, and it has a small set of defeats. This page covers the inclusion-specific ones, the appended-extension problem, and points you at the path-traversal cluster for the generic traversal-string bypasses rather than repeating them. Practice on a free live target.
Step 1: detect the appended extension
Vulnerable include code often adds an extension so the parameter only loads a certain file type. Reveal it by requesting a path that cannot exist and reading the error:
GET /index.php?page=/nonexistent HTTP/1.1
Host: target.exampleHTTP/1.1 200 OK
Content-Type: text/html
Warning: include(/var/www/pages/nonexistent.php): Failed to open stream: No such file or directory in /var/www/html/index.php on line 3The .php the server added is the obstacle: pointing at /etc/passwd would try /etc/passwd.php, which does not exist. The leaked include path (/var/www/pages/) is a bonus.
Null-byte truncation (legacy)
On old PHP, a null byte terminated the path before the appended extension, so the suffix was cut off:
?page=/etc/passwd%00
?page=../../../../etc/passwd%00.phpThis died in PHP 5.3.4 (2010), where a null byte in a path became a hard error. Only genuinely old stacks are affected, so treat it as a legacy probe, not a modern bypass.
Wrappers help, but only for .php source
A common half-truth is that php://filter "strands" the appended extension. It does not. The .php is concatenated onto the whole string, so resource=/etc/passwd becomes resource=/etc/passwd.php, which does not exist. What the wrapper genuinely buys you under an append regime is reading PHP source, because the files you most want (the app's own code) already end in .php. Point the resource at the base name and let the append complete it:
GET /index.php?page=php://filter/convert.base64-encode/resource=config HTTP/1.1
Host: target.exampleHTTP/1.1 200 OK
Content-Type: text/html
PD9waHAKJGRiX3VzZXIgPSAnYXBwJzsKJGRiX3Bhc3MgPSAnUzNjcjN0UGFzcyc7Cg==Here the include built resource=config.php, so the wrapper read config.php as base64 source. That is the real win against an appended extension: source disclosure of .php files. To read a non-PHP file like /etc/passwd while the code still appends .php, the wrapper does not help, and you are back to the null byte (legacy) or finding a sink that does not append. See PHP wrappers for the full set.
Path truncation and dot padding (old PHP / Windows)
Very old PHP (roughly pre-5.3) truncated paths past the filesystem length limit, so a long pad could push the appended extension off the end. The trailing-dot trim is a Windows filesystem behavior, not a PHP one. Both are dated and finicky:
?page=../../../etc/passwd/././././. (long pad) pre-PHP 5.3 only, rarely reliable
?page=..\..\..\windows\win.ini. trailing dot trimmed by Windows (NTFS)The generic traversal-string bypasses live elsewhere
If the app strips or blocks ../ itself, those bypasses are identical to the read case and are covered once, in the path-traversal cluster, rather than duplicated here:
....//....//etc/passwd non-recursive strip -> see path traversal
%2e%2e%2f / %252e%252e%252f URL and double encoding -> see path traversal
..%c0%af overlong UTF-8 (legacy) -> see path traversalFull treatment with worked exchanges is in bypassing path traversal filters. They apply to an include parameter unchanged.
Fixing it
- Append the extension by validating, not concatenating. Resolve and canonicalize first, then confirm both directory and extension.
- Reject null bytes and wrapper prefixes (
php://,data://) in the parameter outright. - Allow-list view names so neither a suffix trick nor a wrapper can reach an unintended file.
Full guidance is in preventing file inclusion. Next, close the whole class properly in preventing file inclusion, or for every payload in one place see the cheat sheet.