Special picoCTF 2023 Solution

Published: April 26, 2023

Description

Special autocorrects every shell command, so you must abuse parameter expansion and braces to execute raw commands and leak the flag.

SSH to saturn.picoctf.net on port 56058 with the supplied password.

Experiment with bash parameter expansion to bypass the forced capitalization/rewriting and run arbitrary commands under the hood.

bash
ssh -p 56058 ctf-player@saturn.picoctf.net
bash
d8819d45
bash
${parameter=ls blargh}
bash
${parameter=cat < blargh/flag.txt}
  1. Step 1Probe the sanitizer
    Bare ls or cat are rewritten, but expressions starting with ${ slip through. Test ${parameter=ls} as a smoke check before chaining anything else.
    Learn more

    The Special shell wraps every line in a regex-style rewriter that hunts for known command names at the start of input. The regex sees a literal token; it does not understand the rest of Bash's grammar. The form ${parameter=word} is the assign-default parameter expansion: if parameter is unset, Bash sets it to word and substitutes that value, which then runs as a command. From the sanitizer's point of view the line begins with a brace, not a verb, so it leaves it alone.

    A worked trace makes the asymmetry concrete. Input ${parameter=ls} arrives. The sanitizer scans for ls, cat, cd as the first identifier; finding ${ instead, it passes the line through. Bash then parses parameter expansion, assigns parameter=ls, substitutes ls, and only at that point does the shell try to run the command. The rewriter never gets a second look.

    Empirical edge cases worth confirming on the box: ${ ls} with a leading space is a syntax error (Bash expects an identifier immediately after the brace), and ${ls} with no operator simply expands an unset variable to the empty string. The sanitizer rejects neither, but only the assign-default form (=) actually runs anything. JSX renders the literal braces with { and } escapes; the shell line itself is plain.

    The technique generalises across restricted shells: rbash, git-shell, and CTF jails all leak when the filter is built on string matching rather than a full Bash parser. The wider toolbox covers aliases, functions, command substitution $(cmd), process substitution <(cmd), brace expansion, and SUID detours through vim :! or awk system(). See Linux CLI for CTFs for a broader survey of these patterns.

  2. Step 2Chain the exploit
    Use ${parameter=ls blargh} to enumerate the directory and ${parameter=cat < blargh/flag.txt} to read the flag through stdin redirection.
    Learn more

    The redirection trick is the second half of the bypass. The sanitizer matches arguments to commands; it does not parse redirection operators. cat < blargh/flag.txt opens the file as cat's stdin instead of passing it as argv[1], so a filter that blocks cat foo often misses cat < foo. Combine that with the parameter-expansion wrapper and the whole expression sails past the rewriter.

    The same lesson applies to real restricted-shell deployments: kiosk terminals, git-shell, container init shells. Whenever the gate is "match these tokens" rather than "parse the language and enforce a whitelist on the AST," the gate leaks. The only durable fix is to swap the shell for a non-shell interface or to enforce the boundary at the syscall layer with seccomp or namespaces.

Flag

picoCTF{5p311ch3ck_15_7h3_w0...35}

Any creative use of ${parameter=...} (or similar expansion) that runs cat on blargh/flag.txt yields the answer.

Want more picoCTF 2023 writeups?

Useful tools for General Skills

Related reading

What to try next