CSAW QUALS 2016 - WTF.SH [WEB]
20 Sep 2016 - pimps- Competition: CSAW 2016
- Challenge Name: WTF.SH
- Type: Web
- Points: flag1 (150 pts) + flag2 (400 pts)
- URL: http://web.chal.csaw.io:8001/
I would like to start this write-up saying that this web challenge is one of the coolest and best web challenges that I have ever played in a CTF… Let me explain why:
- The challenge creator “just” wrote a fucking web server using only bash script. Srly… WTF!?!? I Guess this is why he gave this name to the challenge :-P (hahahaha).
- It’s different from the usual web challenges that we see around in other competitions, even though the first main bug was a path traversal/LFI the context where it was applied, at least for me, was completely new.
- To solve this challenge you had to think and learn the behaviours of the bash script language. . For me this was the cherry on the cake in this challenge.
After finishing the challenge, just to satisfy my curiosity, I went to the IRC channel of the competition just to ask Hyper how long it took to come up with this challenge. He answered me: “Well, let me see… I guess it was around 4 months working 1-2h per day”… So for that I want to say thanks to the CSAW team and mad props to Hyper for coming up with this challenge.
Ok… With that said now lets get to the business:
Flag 1
================================================
$ man 1 wtf.sh
WTF.SH(1) Quals WTF.SH(1)
NAME
wtf.sh - A webserver written in bash
SYNOPSIS
wtf.sh port
DESCRIPTION
wtf.sh is a webserver written in bash.
Do I need to say more?
FLAG
You can get the flag to this first part of the
problem by getting the website to run the
get_flag1 command. I heard the admin likes to
launch it when he visits his own profile.
ACCESS
You can find wtf.sh at http://web.chal.csaw.io:8001/
AUTHOR
Written by _Hyper_ http://github.com/Hypersonic/
SUPERHERO ORIGIN STORY
I have deep-rooted problems
That involve childhood trauma of too many
shells
It was ksh, zsh, bash, dash
They just never stopped
On that day I swore I would have vengeance
I became
The Bashman
REPORTING BUGS
Report your favorite bugs in wtf.sh at
http://ctf.csaw.io
SEE ALSO
wtf.sh(2)
CSAW 2016 September 2016 WTF.SH(1)
================================================
The description says that wtf.sh is a web server written in bash and to get the flag1 we should run the command get_flag1. The description also says that the admin user runs this command when he visits his own profile. My first thought was to exploit a XSS vulnerability in order to session hijack the admin user. I actually managed to find a XSS bug in this challenge registering a username as an XSS payload. But after some minutes I realized that this isn’t the objective of this challenge.
So, after some time the first bug that I found in the app was the source code leak of the main script wtf.sh by visiting http://web.chal.csaw.io:8001/wtf.sh. This can be seen in the following image:
Reading the source code we spotted that a functionality to create the sandboxes leaked the name of the other files:
[... code snip ...]
# if we know the IP (via an X-Forwarded-For header), stick the user in a sandbox
# (Cloudflare will fill in this IP in prod, we can also have nginx fill it in in dev if we want)
if contains "X-Forwarded-For" "${!HTTP_HEADERS[@]}"
then
sandbox_dir="$((cksum <<< ${HTTP_HEADERS["X-Forwarded-For"]}) | cut -d\ -f1).sandbox";
# create sandbox if it doesn't exist
if [[ ! -e "${sandbox_dir}" ]]
then
mkdir "${sandbox_dir}";
# copy anything that isn't itself a sandbox to the dir
cp -R css index.wtf lib.sh login.wtf logout.wtf new_post.wtf new_user.wtf post.wtf post_functions.sh posts profile.wtf reply.wtf spinup.sh user_functions.sh users users_lookup wtf.sh "${sandbox_dir}";
fi
cd "${sandbox_dir}";
else
log "WARNING: Not sandboxing: no X-Forwarded-For header found!"
fi
[... code snip ...]
Using this information we are able to get the source code of all *.sh files. However we could not get the *.wtf files because they are parsed by the wtf.sh web server. I’ll not paste the source code of the whole challenge here to keep this write-up clean, but Hyper already uploaded it to his GitHub account and you can read all the source from there. I’ll only post here code snippets of important parts to solve the challenge.
After reading the source code we understood how the application works. Basically it create files to represent users and posts. The files generated to represent users are created inside of the {web_root}/users/ directory and the post files inside of the {web_root}/posts/ directory. So, fuzzing the parameters of the app that access users and posts files we found 2 path traversal vulnerabilities. The first one was in http://web.chal.csaw.io:8001/profile.wtf?user=../../../../../../etc/passwd but it only leaks the first line of the included file as shown in the following image:
And the second one was in http://web.chal.csaw.io:8001/post.wtf?post=../ Which leaks the full content of all files inside of the included directory as shown in the following image:
Using the second LFI/Path Traversal bug we were able to leak the source code of all *.wtf
files inside of the web root directory as well as other directories on the server (ex: /etc, /usr/bin, etc…).
Since we already knew that the app creates the user files inside of the {web_root}/users
directory, using the second vulnerability we just injected the path /../users to force the application into getting the contents of the user’s files as shown in the following image:
Using this information, the next step was to perform the usual session hijacking attack to impersonate the admin user and get the first flag. This is shown in the following screenshots:
Flag: flag{l00k_at_m3_I_am_th3_4dm1n_n0w}
Flag 2
$ man 2 wtf.sh
WTF.SH(2) Quals WTF.SH(2)
NAME
wtf.sh - A webserver written in bash
SYNOPSIS
wtf.sh port
DESCRIPTION
wtf.sh is a webserver written in bash.
Do I need to say more?
FLAG
You can get the flag to this second part of
the problem by getting the website to run the
get_flag2 command. Sadly, I can't seem to find
anything in the code that does that :( Do you
think you could take a look at it for me?
ACCESS
You can find wtf.sh at http://web.chal.csaw.io:8001/
AUTHOR
Written by _Hyper_ http://github.com/Hyperâ€
sonic/
SUPERHERO ORIGIN STORY
I have deep-rooted problems
That involve childhood trauma of too many
shells
It was ksh, zsh, bash, dash
They just never stopped
On that day I swore I would have vengeance
I became
The Bashman
REPORTING BUGS
Report your favorite bugs in wtf.sh at
http://ctf.csaw.io
SEE ALSO
wtf.sh(1)
CSAW 2016 September 2016 WTF.SH(2)
===================================================
This challenge was the one that almost drove me crazy (lol). Initially we tried to use both the LFI in post.wtf and profile.wtf to read the second flag. In the first attempt we read the /etc/passwd and discovered two users: flag1 (/home/flag1) and flag2 (/home/flag2). We tried to read the .bash_history files and other stuff inside of those users directories, but no success.
We also discovered that inside of those user’s directories existed the files /home/flag1/flag1.txt and /home/flag2/flag2.txt but we couldn’t read them as well. So finally we read the /usr/bin directory to find 2 binaries get_flag1 and get_flag2. They had some string references to those flag files and we assumed that those binaries had some kind of permission to read the flags smashing any hopes of getting the flag without RCE.
Reading the source of the files again I assumed that the spinup.sh file was giving us some hints with the comment: We don’t do whole webroot since we want the people to be able to create files in webroot, but not overwrite existing files.
So the next step was to read the source code again, and again, looking for pieces of code that could allow us to create files in the application. Eventually I spotted that the reply function inside of post_functions.sh also had a path traversal vulnerability via the post_id parameter that comes from the reply.wtf page leading us to control the directory where the reply file was saved. As a proof of concept I injected the path ../../../../tmp to force the app to create the reply file inside of the /tmp directory and used the first LFI vulnerability to verify if the file was created. It was successfully created! The reply function code can be seen below:
function reply {
local post_id=$1;
local username=$2;
local text=$3;
local hashed=$(hash_username "${username}");
curr_id=$(for d in posts/${post_id}/*; do basename $d; done | sort -n | tail -n 1);
next_reply_id=$(awk '{print $1+1}' <<< "${curr_id}");
########## That's the important line! ############
next_file=(posts/${post_id}/${next_reply_id});
##################################################
echo "${username}" > "${next_file}";
echo "RE: $(nth_line 2 < "posts/${post_id}/1")" >> "${next_file}";
echo "${text}" >> "${next_file}";
# add post this is in reply to to posts cache
echo "${post_id}/${next_reply_id}" >> "users_lookup/${hashed}/posts";
}
So the next step was force the app create a file inside of our webroot and verify if it was create with success as can be seen in the following screenshots:
Nice!! Now we control where the reply files can be created, but the most important thing, “How do we kill the fucking suffix ${next_reply_id}!? “… My first thinking was obvious: “Maybe a null byte can do the job!?” but my plans were frustrated one more time :-(. So I decided to share my ideas with the team to get some input. Lanjelot, a team mate, knew that using a space character in the ${post_id} parameter would transform the expression (posts/${post_id}/${next_reply_id}) in an array containing two elements: post/ and /1. In bash, when you have an array of two or more elements and you don’t define the index of that array, it will always return the first element. So he sent the following code to me as proof of concept:
Scorpius:csaw_2016 pimps$ cat teste.sh
next_file=(xxxx yyyy)
echo ${next_file}
Scorpius:csaw_2016 pimps$ sh teste.sh
xxxx
Scorpius:csaw_2016 pimps$
EUREKA!!!
So, to get RCE and consequently the flag I just injected a filename with the wtf extension and a space character on the end of the filename forcing the app to get only the first element of the array (/../xxx.wtf /1). In the text field I inserted a get_flag2 command with a $ symbol at the beginning of the line to force the app to parse it as a command line. This is shown in the following images:
Flag: flag{n0b0dy_exp3c75_th3_p4r3nth3s1s_1nqu1s1t10n}
That’s all folks!!
Hope you enjoyed the reading and one more time thanks a lot to CSAW team for once again make a fucking awesome CTF!