Description
The Multiverse is within your grasp! Unfortunately, the server that contains the secrets of the multiverse is in a universe where keyboards only have numbers and (most) symbols.
Setup
SSH to mimas.picoctf.net on port <PORT_FROM_INSTANCE> with password <PASSWORD_FROM_INSTANCE>.
Experiment with the restricted shell to learn which characters it accepts and how it responds to your input.
Solution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1
Capture an error message into a variableObservationI noticed the shell blocks all alphabetic input, which suggested the only source of letters available was the shell's own error output, making it necessary to trap a predictable error like 'bash: $: command not found' into a variable for later indexing.Pick an invalid token like $ or ? and trap its error output in a variable. _1=$ 2>&1runs the bare $ as a command, redirects stderr into stdout, and stores "bash: $: command not found" in $_1. Verify with echo "$_1" - if you see that single line printed back, you have your character source.bash_1=`$ 2>&1`bashecho "$_1"Expected output
bash: $: command not found
What didn't work first
Tried: Using $() instead of backticks to capture the error, without the 2>&1 redirect
$(_1=$($ )) captures only stdout, so _1 ends up empty because bash error messages go to stderr. echo "$_1" prints nothing, leaving you with no character source. The fix is to add 2>&1 inside the capture so stderr is merged into stdout before backticks or $() grabs it.
Tried: Running a random word like foo 2>&1 instead of $ to trigger the error
Letters are blocked by the restricted shell, so typing foo results in an immediate rejection before any error message is generated. The trick requires using a non-letter token such as $ or ? that the shell actually tries to evaluate and then fails on, producing the 'command not found' string containing the letters you need.
Learn more
The keyboard restriction blocks letters, but parameter expansion (
${_1:N:1}) does not require typing letters - it indexes into a string by position. By trappingbash's own error output we get a long string full of letters we can index into.$as a standalone command is the cleanest trigger because it parses, fails immediately, and produces exactly one line:bash: $: command not found.?works similarly. Wrapping in backticks runs the command in a subshell and captures its output; the2>&1redirection sends stderr (where the error actually lives) onto stdout so it can be captured. Without that redirection$_1would silently end up empty, and the next step would fail with no useful diagnostic.Step 2
Extract characters with parameter expansionObservationI noticed the captured error string 'bash: $: command not found' contains common lowercase letters at fixed, predictable positions, which suggested using bash's ${var:offset:length} substring syntax to pluck individual characters without typing any letters.Bash ${var:offset:length} slices a substring out of a variable. Test locally first: echo "${_1:9:1}" should print c, and echo "${_1:10:1}" should print o. From the captured string "bash: $: command not found", you can pluck b/a/s/h/c/o/m/n/d/t/f/u as needed.Learn more
This is the heart of the trick. The captured error string contains the lower-case alphabet's commonly-used letters in fixed positions, so you can spell short command names like
echo,cat,sh, orbashby chaining slices.String: bash: $: command not found Position: 01234567890123456789012345 1111111111222222 Useful indices (verify with: echo "${_1:N:1}"): ${_1:0:1} = b ${_1:1:1} = a ${_1:2:1} = s ${_1:3:1} = h ${_1:9:1} = c <- needed for /bin/echo ${_1:10:1} = o <- needed for /bin/echo ${_1:13:1} = a ${_1:15:1} = d ${_1:17:1} = n ${_1:19:1} = t ${_1:23:1} = u ${_1:24:1} = nStep 3
Locate the flag file with a globObservationI noticed that shell wildcards like * and ? require no letters to type, which suggested using a glob such as ./*/* to enumerate files in the current directory even without access to ls, and that bash would reveal the path in an error message when it tried to execute the matched file.Run ./*/* and bash expands the glob to whatever paths exist; the listing here shows the flag at ./blargh/flag.txt, matching pattern ./*/????.???.bash./*/*Expected output
bash: ./blargh/flag.txt: Permission denied
What didn't work first
Tried: Running /* or /* /* to enumerate files instead of ./*/*
/* expands from the filesystem root and produces a long list of system paths, not the challenge directory contents. The error output becomes noisy and does not reliably reveal ./blargh/flag.txt. Starting with ./ anchors the glob to the current working directory where the flag actually lives.
Tried: Trying to read the flag directly after seeing the path in the error message
The shell printed 'Permission denied' when it tried to execute ./blargh/flag.txt as a command, which means direct execution is blocked. The path is now known, but you still need to read it with a constructed cat or process substitution - the next steps cover how to do that without typing any letters.
Learn more
./*/*expands to the list of files exactly one directory deep below the current directory. Bash tries to execute the first match as a command, which (helpfully) fails and prints the file path in the error message - giving you the path even when you can't type letters tols. The pattern./*/????.???later picks out the same path by structure (4-letter name, 3-letter extension).Step 4
Build /bin/echo from globs and slicesObservationI noticed that /bin is exactly 3 characters and echo is 4 characters, which suggested that combining a ? wildcard path like /???/?c?o with parameter-expansion slices for the letters 'c' and 'o' from $_1 would reliably resolve to /bin/echo without typing any forbidden letters.Glob /???/?${_1:9:1}?${_1:10:1} into the path /bin/echo. The /??? matches /bin (any 3-char directory under /), and the inner ?c?o pattern matches "echo" via positions 9 and 10 of $_1. The earlier attempt at /usr/bin/cat (/?${_1:2:1}?/???/??${_1:19:1}) failed because the ?s? prefix didn't expand to /usr on the box (the glob found no matches and bash printed "bash: /?s?/???/??t: No such file or directory").bash/???/?${_1:9:1}?${_1:10:1}What didn't work first
Tried: Trying to reach /usr/bin/cat with /?${_1:2:1}?/???/??${_1:19:1} because cat is more familiar than echo
The glob /?s?/???/??t requires /usr to exist as a 3-character directory under /, but on this machine the layout does not match: no subdirectory of / fits ?s? with a middle s. Bash leaves the literal unmatched pattern in place and the shell rejects it as 'No such file or directory'. /bin/echo is reachable with the simpler /???/?c?o pattern because /bin is exactly 3 characters and echo is 4.
Tried: Constructing the full path as a string in a variable, then running it with ${_cmd}
Assigning _cmd='/bin/echo' is blocked because the assignment itself contains letters. Even if you built the string through concatenation of parameter-expansion slices, invoking it via ${_cmd} does not work the same way as a bare glob - bash does not perform word-splitting and glob-expansion on the result of a variable reference used as a command name in the same pass. Running the glob directly as the command token is the reliable approach.
Learn more
Verify the glob expands the way you expect before using it as the command:
echo /???/?${_1:9:1}?${_1:10:1}should print/bin/echoand nothing else. If it prints multiple matches or the literal pattern, the glob is wrong and the command will not execute echo.When a glob has no match, bash on this image leaves the literal pattern in place, so the failure surface is "
/?s?/???/??t: No such file or directory" - the unmatched pattern itself appears in the error, which doubles as a diagnostic. That's the failure mode you should expect, and how you knew the/usr/bin/catpath's middle?s?wedge wasn't hitting/usr.Step 5
Read the flagObservationI noticed that bash's $(<file) process substitution reads a file's contents as a builtin without spawning any external program like cat, which suggested pairing it with the glob ./*/????.??? and the reconstructed /bin/echo command to print the flag without typing any disallowed letters.Combine the constructed echo with bash process substitution: /???/?${_1:9:1}?${_1:10:1} "$(<./*/????.???)" prints the contents of ./blargh/flag.txt without ever typing the literal letters cat, less, or grep.bash/???/?${_1:9:1}?${_1:10:1} "$(<./*/????.???)"Expected output
picoCTF{7h15_mu171v3r53_15_m4dn355_145...}What didn't work first
Tried: Using $(cat ./blargh/flag.txt) instead of $(<./blargh/flag.txt) to read the file
The word cat contains letters, which the restricted shell blocks before the command can even run. $(<file) is a bash builtin read-file form that requires no external program and no letters, making it the only viable option in this environment.
Tried: Passing the glob ./*/????.??? directly as a shell argument without the $(<...) wrapper
Without the $(<...) wrapper, the glob expands to the file path ./blargh/flag.txt as a string, not its contents. echo would just print the path, not the flag. The $(<...) layer is what causes bash to open and read the file before passing the result to echo.
Learn more
$(<file)is a bash builtin that reads the contents offilewithout spawningcat; it's the bash idiom for "file slurp." Combined with the globbed-upecho, the final command never types any disallowed character but still prints the flag contents.
Flag
Reveal flag
picoCTF{7h15_mu171v3r53_15_m4dn355_145...}
There are multiple paths through this challenge - the key insight from the hint 'Where can you get some letters?' is to harvest characters from error messages and use bash parameter expansion to build valid commands.