Dice CTF Quals 2024
Zshfuck [127 pts]
may your code be under par. execute the getflag binary somewhere in the filesystem to win
nc mc.ax 31774
We’re given a “zsh” jail file:
#!/bin/zsh
print -n -P "%F{green}Specify your charset: %f"
read -r charset
# get uniq characters in charset
charset=("${(us..)charset}")
banned=('*' '?' '`')
if [[ ${#charset} -gt 6 || ${#charset:|banned} -ne ${#charset} ]]; then
print -P "\n%F{red}That's too easy. Sorry.%f\n"
exit 1
fi
print -P "\n%F{green}OK! Got $charset.%f"
charset+=($'\n')
# start jail via coproc
coproc zsh -s
exec 3>&p 4<&p
# read chars from fd 4 (jail stdout), print to stdout
while IFS= read -u4 -r -k1 char; do
print -u1 -n -- "$char"
done &
# read chars from stdin, send to jail stdin if valid
while IFS= read -u0 -r -k1 char; do
if [[ ! ${#char:|charset} -eq 0 ]]; then
print -P "\n%F{red}Nope.%f\n"
exit 1
fi
# send to fd 3 (jail stdin)
print -u3 -n -- "$char"
done
Looking up zsh, I found out it was essentially just a Unix shell that’s typically used in Macs. It didn’t seem to actually impact the problem – i.e. most Linux commands work just the same.
Taking a look through the source code, the only constraints seemed to be 1. only 6 characters were allowed for use during each session and 2. the characters of “*”, “?”, and “`” were blacklisted. THe blacklisted characters are two wildcards (* and ?) and a backtick character, which allows for certain command executions.
Immediately, I figured wildcards might be important for this challenge. I looked up wildcards for Linux, and found this site documenting all of them. Importantly, doing something like [!x]
seemed to be potentially very useful, as it act basically like a ?
.
After doing this, I ran ls -al
on the service. This showed the following:
total 16
drwxr-xr-x 1 nobody nogroup 4096 Feb 2 21:56 .
drwxr-xr-x 1 nobody nogroup 4096 Feb 2 13:31 ..
-rwxr-xr-x 1 nobody nogroup 795 Feb 2 21:55 run
drwxr-xr-x 1 nobody nogroup 4096 Feb 2 13:31 y0u
Testing run with ./run
shows that it’s just the binary version of the jail source file. We also have a directory named y0u
. Let’s list that out with ls y0u
.
w1ll
At this point, I figured it was going to be several nested directories spelling out some message starting with y0u/w1ll
. Unfortunately, I wasn’t sure how to list the next directories. In fact, I spent the next hour trying to figure out how to call ls
on the next file using only 6 characters (I could only get it to work with 7) or test all possible directory name lengths with [!.]
acting as a wildcard.
Eventually, I realized that ls
probably had a recursive function… wasted an hour of my life because of this T^T
Sending ls -R
to the service, I got:
.:
run
y0u
./y0u:
w1ll
./y0u/w1ll:
n3v3r_g3t
./y0u/w1ll/n3v3r_g3t:
th1s
./y0u/w1ll/n3v3r_g3t/th1s:
getflag
So that’s our path! ./y0u/w1ll/n3v3r_g3t/th1s/getflag
, Because of our useful wild card [!.]
, we can specify our charset as ./[!]
and send ./[!.][!.][!.]/[!.][!.][!.][!.]/[!.][!.][!.][!.][!.][!.][!.][!.][!.]/[!.][!.][!.][!.]/[!.][!.][!.][!.][!.][!.][!.]
to execute the getflag binary.
dice{d0nt_u_jU5T_l00oo0ve_c0d3_g0lf?}
TL;DR: Not knowing ls had a recursive option cost me an hour of my life.