Skip to main content

VNE picoCTF 2023 Solution

The privileged binary takes a directory from an unsanitised environment variable, so inject a command and run as root.

Published: April 26, 2023Updated: August 25, 2026

Description

We have a binary that can list directories as root. SSH into the provided server, run the binary named bin, and find a way to read the root flag through it.

SSH into the provided server as ctf-player using the supplied password and port.

Locate the SUID binary named bin in your home directory and run it once to see how it behaves.

bash
ssh ctf-player@saturn.picoctf.net -p <PORT_FROM_INSTANCE>

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
  1. Step 1Observe default behavior
    Observation
    The binary is SUID root and the challenge says it lists directories as root. Run it bare first and let it tell you what input it wants.
    Running ./bin with nothing set prints: Error: SECRET_DIR environment variable is not set. The binary uses the SECRET_DIR value as the directory it lists, and it does the listing as root because it is SUID.
    bash
    ./bin

    Expected output

    Error: SECRET_DIR environment variable is not set
    What didn't work first

    Tried: Run strings or ltrace on the binary to discover which environment variable it uses before running it

    strings does show SECRET_DIR somewhere in its output, but the binary names the missing variable itself the moment you run it. That is faster and more reliable than reading hundreds of lines of library symbols.

    Tried: Check file permissions with chmod or sudo -l before investigating the binary's behavior

    What matters is the SUID bit, the 's' in the owner execute position of ls -l, not write permissions or sudo rules. sudo -l shows nothing for ctf-player, and chmod on a binary you do not own is denied. The path up is the binary's own execution.

    Learn more

    A SUID binary runs with the privileges of its owner (here, root) regardless of who launches it. This binary lists the contents of whatever directory the SECRET_DIR environment variable points to, but performs that listing with root privileges. The intended use is harmless directory browsing; the bug is in how it constructs the listing command.

    Before crafting anything, run the binary normally to learn which environment variable it reads. The error message names it directly: SECRET_DIR. You can also confirm the SUID bit with ls -l bin (look for an s in the owner permission field).

  2. Step 2List directories as root with SECRET_DIR
    Observation
    The error names SECRET_DIR as the variable it wants, and the listing runs as root. Point it at /root and see whether the flag is there.
    Set SECRET_DIR on the command line. Pointing it at /root shows that the flag file exists there, but a normal cat on it is denied because the file itself is only readable by root.
    bash
    SECRET_DIR=. ./bin
    bash
    SECRET_DIR=/root ./bin
    bash
    cat /root/flag.txt
    The listing of /root reveals flag.txt, but a direct cat /root/flag.txt returns Permission denied because only the binary runs as root, not your shell.
    What didn't work first

    Tried: Copy the binary to your home directory and run it from there, hoping the SUID privilege carries over to let you cat files afterward

    Copying a SUID binary strips the bit, since regular users cannot create SUID executables. The copy runs as ctf-player, so the listing may still work but reading the flag is denied. The privilege exists only while the original binary is running.

    Tried: Use sudo cat /root/flag.txt after confirming the flag is at /root/flag.txt

    ctf-player has no sudo rights here, as sudo -l confirms. The only root execution available is the SUID binary, so the binary has to run the cat for you.

    Learn more

    Supplying SECRET_DIR=/root confirms the flag location, but you still cannot read the file directly. The binary holds the root privilege, your interactive shell does not. The path forward is to make the privileged binary itself read the file rather than just listing the directory.

    This is the classic split that makes these challenges solvable: the privileged process accepts attacker-controlled input (the environment variable) and feeds it into a shell command without sanitizing it.

  3. Step 3Inject a command through SECRET_DIR
    Observation
    SECRET_DIR goes into a root shell command with no validation. Append a metacharacter such as & or ; and you chain a second privileged command onto it.
    Because the binary passes SECRET_DIR into a shell, you can append your own command with a shell separator. Using & (or ;) chains a cat of the flag that runs as root, printing the flag.
    bash
    SECRET_DIR="/root&cat /root/flag.txt" ./bin
    What didn't work first

    Tried: Use a null byte as the separator instead of & or ; to chain the second command

    A NUL terminates the C string, so everything after it is cut off before the value ever reaches the shell and only the /root part survives. A literal newline does survive an environment variable and would work as a separator, but you have to smuggle it in with $'\n' quoting, which is fiddly for no gain. & and ; are plain characters that pass through untouched, and a pipe works too if the binary uses sh -c, since cat ignores what arrives on stdin here.

    Tried: Wrap the injection in a subshell with $() - for example SECRET_DIR="/root $(cat /root/flag.txt)" - rather than using &

    A $() substitution is expanded by the shell that sets the variable, which is your unprivileged one, not the root shell inside the binary. It runs as ctf-player and hits permission denied before the binary even starts. The injection has to reach the binary as a literal string.

    Learn more

    This is a command injection vulnerability. The binary takes the SECRET_DIR value and interpolates it into a shell command line (something like ls $SECRET_DIR) that runs with root privileges. Because the value is never escaped, a shell metacharacter such as &, ;, or | breaks out of the intended command and lets you run a second one.

    Setting SECRET_DIR="/root&cat /root/flag.txt" makes the binary first list /root, then run cat /root/flag.txt as root, which prints the flag inline with the listing output. The fix in real software is to never build shell commands from untrusted input: pass arguments to execve directly, or strictly validate and quote any value before it reaches a shell.

  4. Step 4Read the flag
    Observation
    The injected cat runs as root and prints the flag inline with the directory listing, so it is right there to copy.
    The injected cat prints the flag value alongside the directory listing. Copy it and submit.
    Learn more

    Command injection through an unsanitized environment variable is a real and recurring class of privilege-escalation bug. Whenever a privileged program builds a shell command from data an unprivileged user controls (environment variables, file contents, network input), an attacker can chain arbitrary commands at the elevated privilege level.

Interactive tools
  • Regex TesterTest regular expressions against a string with live match highlighting, flag toggles, and common CTF pattern shortcuts.
  • Strings ExtractorPull printable text from any binary, library, or image. ASCII and UTF-16 detection, configurable minimum length, flag-like highlight, no command line needed.

Flag

Reveal flag

picoCTF{Power_t0_man!pul4t3_3nv_...}

Per-instance flag. The prefix picoCTF{Power_t0_man!pul4t3_3nv_ is consistent across multiple verified sources but the 8-character hex suffix varies per instance.

Key takeaway

A SUID binary hands elevated privileges to anyone who runs it, and if it feeds attacker-controlled input into a shell without escaping metacharacters, the attacker inherits those privileges through the injected command. Environment variables are an easy surface to overlook, because attention goes to arguments and network input. The fix is to pass data straight to execve instead of building a shell string. The same bug shows up in real CVEs against setuid helpers, sudo wrappers, and cron scripts.

Related reading

Useful tools for Binary Exploitation

Where to go next