Secure Steps

Cybersecurity assessments, insights, and clear remediation.

Category:

LEET_AS_A_SERVICE Walkthrough — Arbitrary File Read via CLI-Argument Injection

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)

  1. 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/subprocess with a list), never through a shell string.
  2. 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].
  3. 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.

en_US
Secure Steps
Privacy Overview

This website uses cookies so that we can provide you with the best user experience possible. Cookie information is stored in your browser and performs functions such as recognising you when you return to our website and helping our team to understand which sections of the website you find most interesting and useful.