CSAW QUALS 2016 - WTF.SH [WEB]


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:

Source Code Leaking

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:

LFI via path traversal

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:

LFI via path traversal 2

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:

User sessions and password hashes

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:

Change session cookie

Session Hijacked and getting first flag

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:

Creating a reply file inside of the webdir

File create with success! YOLO

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:

Creating the malicious page

Getting the flag

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!

FLAAAAAAAAAGS

pimps

FLAAAAAGS... SLOTH LOVE FLAAAAAGS....

Melbourne - Australia https://twitter.com/marcioalm