<-- home

CSAW Quals 2016 / wtf.sh (2)

Description

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)

The Challenge

This is the second part of the CSAW WTF web challenge.

This challenge consists of 100% bash web server in which we have to try to execute the get_flag2 command on. Unlike the first challenge wtf.sh (1), the get_flag2 is not available in the code, so the goal of the challenge is to obtain RCE.

Obtaining the Source Code

Like in the first challenge wtf.sh (1), we need to leak the source code to know what we’re dealing with.

We can do so through a file traversal vulnerability in the post.wtf page.

Browsing to http://web.chal.csaw.io:8001/post.wtf?post=../ will leak the contents of some of the source code files:

get_source

One can notice through the leaked source code some calls to the source bash command. This gives us some idea of what the filenames are; we can leak these files directly now.

Here are the available files:

  • http://web.chal.csaw.io:8001/user_functions.sh
  • http://web.chal.csaw.io:8001/post_functions.sh
  • http://web.chal.csaw.io:8001/spinup.sh
  • http://web.chal.csaw.io:8001/wtf.sh
  • http://web.chal.csaw.io:8001/lib.sh

Finding the Vulnerability

As we’ve mentionned earlier, the goal of the challenge is to obtain RCE somehow.

Looking through the source files, we see the core function for displaying webpages (include_page in the wtf.sh file):

max_page_include_depth=64
page_include_depth=0
function include_page {
# include_page <pathname>
local pathname=$1
local cmd=""
[[ "${pathname:(-4)}" = '.wtf' ]];
local can_execute=$?;
page_include_depth=$(($page_include_depth+1))
if [[ $page_include_depth -lt $max_page_include_depth ]]
then
local line;
while read -r line; do
# check if we're in a script line or not ($ at the beginning implies script line)
# also, our extension needs to be .wtf
[[ "$" = "${line:0:1}" && ${can_execute} = 0 ]];
is_script=$?;

# execute the line.
if [[ $is_script = 0 ]]
then
cmd+=$'\n'"${line#"$"}";
else
if [[ -n $cmd ]]
then
eval "$cmd" || log "Error during execution of ${cmd}";
cmd=""
fi
echo $line
fi
done < ${pathname}
else
echo "<p>Max include depth exceeded!<p>"
fi
}

Starting from the top of the function, we can understand that:

  • The URI that we request is sent to the pathname variable.
  • The pathname must end with .wtf
  • The command that we want to execute must be prefixed with the $ character.
  • Later down the road, the command is executed through eval

Knowing this, we look around the source code to find potential file upload vulnerabilities.

… Which brings us to the reply function in post_functions.sh:

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}");
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";
}

This function adds a reply to a post by creating a file on the filesystem. It is called through a POST request at http://web.chal.csaw.io:8001/reply.wtf?post=efG5D.

Here are the lines of code responsible for creating a file on the filesystem :

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}";

The interesting part of those lines is the first one, which assigns an array to the next_file variable for some reason (notice the ()). This line of code is also vulnerable to path traversal, as we control the post_id value.

With this in mind, we can easily set next_file to the value of our choice.

Let’s send a POST request to http://web.chal.csaw.io:8001/reply.wtf?post=../payload.wtf+ (notice the added url-encoded space character at the end).

Inside the reply function, next_file will be assigned the following : (posts/../payload.wtf /1).

Bash arrays are created by seperating each element in the array by spaces. If we want to create an array with elements from 1 to 5, we would do array=(1 2 3 4 5). Now if we want to print the first item of an array, we would do echo ${array[0]}, or simply echo ${array}.

Therefore, our payload will create an array next_file with two elements : posts/../payload.wtf and /1. Since ${next_file} will return the first element of the array, the following lines :

echo "${username}" > "${next_file}";
echo "RE: $(nth_line 2 < "posts/${post_id}/1")" >> "${next_file}";
echo "${text}" >> "${next_file}";

… will redirect output to posts/../payload.wtf.

Which makes are payload accessible through http://web.chal.csaw.io:8001/payload.wtf.

Getting the Flag

Now that we can write arbitrary files, we need to execute the get_flag2 command.

With the payload from earlier, the payload.wtf file will have the following structure :

my_username
RE: the_original_post_title
my_reply

As we have mentionned earlier, the include_page file only eval’s our code if the line starts with a $. Since we control the username, let’s register a user called $get_flag2.

Rerunning the payload above, we create the file :

$get_flag2
RE: the_original_post_title
my_reply

Requesting the http://web.chal.csaw.io:8001/payload.wtf will now run the get_flag2 command and print the flag.

flag