LEET_AS_A_SERVICE Walkthrough — Arbitrary File Read via CLI-Argument Injection
Platform: CyberTalents · Category: Web · Difficulty: Easy/Medium
Goal: Read /flag.txt from a PHP page that leetifies your input.
Challenge
“1337 Speak Converter” — a PHP page that converts your input to leetspeak. The twist in the challenge description: the PHP developer used a Python script — “a gift from my dad… he is a python programmer and I only know php, so I just used it as it is”.
The flag is in /flag.txt on the server.
The vulnerability: raw user input becomes a CLI argument
The PHP executes the Python converter by dropping the raw POST input into a shell command:
shell_exec("python3 leetify.py " . $_POST['leetify'])
No escaping. No validation. Anything we type lands in the shell command line.
And here’s the gift from dad: the Python script treats sys.argv[1] as a file path when it exists, and prints the file’s contents (leetified):
$ python3 leetify.py /etc/hostname
...hostname content, leetified...
So the whole challenge collapses into: make the script open /flag.txt.
Bypassing the filters
The server blocks a handful of words and characters:
- Blocked:
id,cat,ls,whoami, backticks,$(, real newlines - Not blocked:
/,.,flag,txt,$,{,},*
The client-side JS also blocks ;|&'" and spaces — but that’s just the browser, irrelevant.
Since /, flag, and txt are all allowed, the payload is simply:
leetify=/flag.txt
No injection tricks, no command chains — just the file path as the argument.
Result
/flag.txt -> FL4G{3v3n_my_fl4g5_4r3_l3371f13d}
= FLAG{even_my_flags_are_leetified}
Why this worked (the real lesson)
- Never build shell commands from user input.
shell_exec("python3 leetify.py " . $input)is command/argument injection by construction. Pass arguments through an array (exec/subprocesswith a list), never through a shell string. - CLI tools that accept file paths are file-read primitives. The “gift from dad” script’s convenience feature — “pass a file path and I’ll leetify it” — became an arbitrary file read once attacker-controlled input reached
argv[1]. - Filter lists are not security. The blocklist (
id,cat,ls…) missed/,flag,txt— and any single missed token (or the lack of shell metacharacter requirements) collapses the whole defense.
Defense takeaways:
- Pass arguments as arrays (
subprocess.run([...]),exec(..., array)) — never interpolate into a shell string. - Validate input against a strict allowlist (e.g., alphanumeric + max length) rather than blocking “dangerous” substrings.
- Sandbox/deny file-path arguments for any subprocess that accepts them, or drop that feature entirely.
Files
- Challenge source + payload — a one-shot, no exploit script needed.
Writeup of a solved CyberTalents web challenge. All attack surface described here is part of the challenge’s intended vulnerable application.