cybersecurity · intermediate · ~15 min · safe pentest lab

Path Traversal Detector

Recognize that input filters must account for encoding and separator variants, and practice bounds-safe lookahead scanning where every index is validated against a precomputed length before use.

Challenge

A file-serving web endpoint maps a client-supplied request path onto files under a document root. If the path contains a directory-traversal sequence, an attacker can escape the root and read arbitrary files (for example /etc/passwd). The asset at risk is the server filesystem; the insecure assumption is that a request path is always a well-behaved relative path. Attackers dodge naive filters by mixing separators (/ vs \) and by percent-encoding the dots or the slash so the raw string no longer literally contains ../.

Your job is a defensive detector, not an exploit: implement has_traversal to return 1 when the path contains any traversal marker and 0 otherwise. The function looks ONLY at a fixed string handed to it by the test harness — it never opens a file, touches the network, or reaches a live target. Detect all of these (matching is case-insensitive for the hex digits): the literal ../, the literal ..\, the percent-encoded %2e%2e, and the percent-encoded ..%2f. Deny by default and treat every index into the string as attacker-controlled: measure the length first and bounds-check before you read a lookahead byte, so a marker near the end never causes an out-of-bounds read.

Example

  • has_traversal(safe) -> 0
  • has_traversal(unix_trav) -> 1
  • has_traversal(pct_slash) -> 1

Input format

A single argument: const char *path, a NUL-terminated request-path string (may be NULL, may be empty, may contain any bytes). The string is fixed test data; it is never a live path.

Output format

Return int: 1 if a traversal sequence is present, otherwise 0.

Constraints

C11, freestanding logic only — no I/O, no filesystem, no network access. Do not call any function that opens or stats a file. NULL input must return 0 without dereferencing. Bounds-check every lookahead index against the string length before reading it. Hex-digit matching (the e/f in %2e/%2f) is case-insensitive; the surrounding %, 2, and the literal dots/slashes are matched exactly.

Starter code

int has_traversal(const char *path){
    /* TODO: scan path for ../ ..\ %2e%2e ..%2f (case-insensitive); NULL => 0.
       Measure the length first, then bounds-check every lookahead index
       BEFORE reading it. This stub is intentionally wrong. */
    return -1;
}

Common mistakes

Dereferencing path before the NULL check. Using strstr for only ../ and missing the backslash, percent-encoded, and uppercase variants. Reading path[i+2] (or path[i+5]) without first confirming those indices are within the string, causing an out-of-bounds read on a short string. Doing case-sensitive comparison and missing %2E/%2F. Flagging a bare .. (like ..hidden or a trailing a/..) that has no separator, producing false positives.

Edge cases to handle

NULL path returns 0. Empty string returns 0. A string with dots but no separator (..hidden) returns 0. A trailing .. with no following separator (a/..) returns 0. Traversal in the middle of a longer path is still detected. Uppercase hex (%2E%2E, ..%2F) is detected. A marker whose full pattern would run past the end of the string must not trigger an out-of-bounds read.

Background lessons

Solve this exercise in the browser editor — compile and run against the test harness, no setup required.