Forbidden Paths picoCTF 2022 Solution

Published: July 20, 2023

Description

The site blocks absolute paths but still reads files relative to the web root. Use directory traversal (../../../../flag.txt) to bypass the filter.

Submit filenames through the form.

Absolute paths like /flag.txt are rejected, so supply a relative traversal path: ../../../../flag.txt.

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
  1. Step 1
    Understand the constraint
    Observation
    I noticed the challenge description explicitly states the site blocks absolute paths but still reads files relative to the web root, which suggested the filter is a naive leading-slash check that relative traversal paths can bypass by counting how many ../ segments are needed to climb out of the four-level webroot (/usr/share/nginx/html).
    The prompt reveals the webroot (/usr/share/nginx/html) and that the actual flag is /flag.txt. Count the slashes in the webroot to know how many ../ you need - here four levels (usr, share, nginx, html) drop you at /. If the webroot isn't disclosed, start with one ../ and add another each request until you read a known file like /etc/passwd.
    What didn't work first

    Tried: Trying just one or two ../ sequences like ../../flag.txt.

    This doesn't climb high enough out of the webroot. The webroot is four directory levels deep under /, so fewer than four ../ segments still leave you inside the nginx html tree where there is no flag.txt. You need exactly as many ../ as there are path segments in the webroot path.

    Tried: Trying the absolute path /flag.txt directly in the form.

    The challenge filter explicitly blocks inputs that start with /, which is the most obvious bypass attempt. That is exactly what the prompt warns about. Relative traversal paths do not start with a slash, so ../../../../flag.txt (no leading slash) passes the check while still resolving to the root-level file.

    Learn more

    Path traversal (also called directory traversal) is a vulnerability where an application uses user-supplied input to construct a file path without properly sanitizing ../ sequences. Each ../ moves one directory level up in the filesystem hierarchy, so enough of them can escape the intended directory entirely.

    The web root /usr/share/nginx/html is four levels deep from the filesystem root (/). Prepending ../../../../ to any filename therefore resolves to the filesystem root, allowing you to read any file the web server process has permission to access - including /flag.txt, /etc/passwd, or application configuration files containing database credentials.

    This attack is catalogued as CWE-22 (Improper Limitation of a Pathname to a Restricted Directory) and appears regularly on the OWASP Top 10. The correct fix is to canonicalize the resolved path with something like os.path.realpath() in Python or realpath() in C, then verify it starts with the intended base directory - blocking any traversal that escapes the sandbox.

    Beyond web servers, path traversal vulnerabilities appear in desktop applications, archive extraction code, and file upload handlers. A classic variant is the Zip Slip attack: a specially crafted zip file contains entries with filenames like ../../etc/cron.d/evil. If the extraction library naively joins the output directory with the entry filename without checking the result, it writes files outside the intended directory - potentially overwriting system files. Many languages and frameworks patched this class of bug after coordinated disclosure in 2018.

    When testing for path traversal during a security assessment, try both forward slashes (../) and backslashes (..\), which Windows systems also interpret as directory separators. Additionally, null-byte injection (../../../etc/passwd%00.jpg) used to fool older PHP versions into ignoring a required file extension. While modern runtimes have fixed null-byte handling, these techniques illustrate why input sanitization must handle all possible encodings and not just the happy path.

  2. Step 2
    Traverse upward
    Observation
    I noticed the webroot is exactly four directories deep under / (usr, share, nginx, html), which suggested that submitting ../../../../flag.txt as a relative path would climb precisely back to the filesystem root and reach /flag.txt without triggering the leading-slash filter.
    Entering ../../../../flag.txt climbs out of the webroot and reads the real flag file. If a follow-up filter blocks literal ../, percent-encoding tricks (%2e%2e%2f, double-encoded %252e%252e%252f, mixed ..%2f) sometimes round-trip through naive sanitizers - try those only after the canonical traversal fails.
    What didn't work first

    Tried: Immediately jumping to percent-encoded variants like %2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fflag.txt before trying the plain form.

    This challenge's filter only blocks leading slashes, not ../ sequences, so encoding tricks are unnecessary here. Trying encoded forms first wastes time and can produce confusing errors if the server decodes and re-encodes the path in unexpected ways. Always try the plain ../../../../flag.txt first and only escalate to encoding tricks when that is blocked.

    Tried: Appending the traversal to a relative filename like images/../../../../flag.txt assuming a subdirectory prefix is required.

    The form accepts a bare traversal path without any leading directory component. Adding an extra prefix like images/ just pushes the starting point deeper, requiring more ../ steps and making the math harder. Start the traversal directly from the webroot by submitting ../../../../flag.txt with no prefix.

    Learn more

    Simple blocklist-based filters that reject strings starting with / are easily bypassed because relative traversal paths don't begin with a slash. More sophisticated filters might also block ../ directly, but those can often be defeated with URL encoding (%2e%2e%2f), double encoding (%252e%252e%252f), or mixed representations (..%2f) - demonstrating why blocklists are inherently fragile compared to allowlist-based validation.

    In real penetration testing, path traversal findings are high-severity because they can expose configuration files, source code, private keys, and database files. Automated scanners like Burp Suite and nikto include path traversal checks, and manual testers look for any parameter that appears to reference a filename, especially those with extensions like .txt, .php, or .log.

    A useful recon file to target is /proc/self/environ, which on Linux exposes the environment variables of the running web server process. This can leak database connection strings, API keys, and other secrets injected via environment variables - a common deployment pattern in containerized applications. Another classic target is /proc/self/cmdline, which reveals the exact command used to start the process and can hint at configuration file locations.

    Always pair path traversal testing with a check of /etc/passwd to confirm the vulnerability is exploitable before reaching for more sensitive targets. A successful read of /etc/passwd (which is world-readable by design) proves traversal works and lets you enumerate system users without risking detection from reading genuinely sensitive files like private SSH keys.

    See Web challenges: real-world bug patterns for the larger family of input-trust failures (open redirects, SSRF, prototype pollution) that share this root cause.

Flag

Reveal flag

picoCTF{...}

Classic path traversal-relative paths often slip past simple filtering.

Key takeaway

Path traversal (CWE-22) occurs when an application concatenates user-supplied input onto a base directory path without canonicalizing the result and verifying it still starts with the intended prefix. Each ../ segment climbs one directory level, and enough of them escape the sandbox entirely. The correct fix is os.path.realpath() followed by a startswith check, not a blocklist; blocklists fail against URL encoding, double encoding, null bytes, and backslash variants. The same class of bug appears in zip extractors (Zip Slip), file upload handlers, and template engines anywhere a filename is derived from external input.

Related reading

Want more picoCTF 2022 writeups?

Useful tools for Web Exploitation

What to try next