CSAW Quals 2016 – wtf.sh (1)

wtf.sh was a forum-like webapp, supposedly written in bash. Users could register accounts, log in, create posts, and reply to posts. There were two challenges involving wtf.sh; wtf.sh(1) worth 150 points, and wtf.sh(2), worth 400 points.


I started taking a look at wtf.sh late Saturday night. The challenge info for wtf.sh tells you that your goal is to get the server to run the commands get_flag1 (for the first flag) and get_flag2 (for the second flag). For the first flag, wtf.sh(1), they also provide the useful information that “the admin likes to launch get_flag1 when he visits his own profile”.

Okay, so it seems like we want to log in as the admin somehow. Let’s take a look at what happens when we log in normally.


Looks like the site sets two cookies; a “USERNAME” cookie and a “TOKEN” cookie. So if we can somehow steal the admin accounts “TOKEN” cookie (or password), we can log in as the admin.

At this point, the first idea that came to mind (wtf.sh being a forum application) was to do some sort of cross-site scripting (XSS) attack. This was further encouraged by the fact that the site did not escape things correctly everywhere: for example, if you try registering or logging in as the user “alert(“Hi!”)”, you’ll be presented with a lovely alert message.


At this point, my plan was to register a user with some cookie-stealing script in their username, create a post with that user, and then get the admin to view that post (hopefully leaking their TOKEN cookie to me). Unfortunately, there were a couple difficulties with this approach:

  • Even though the username was unescaped on the login/registration pages, it was escaped on the post listing page. I tried checking for other XSS vulnerable fields (post title, post content, replies) but everything seemed to be escaped correctly.
  • It was unclear how to actually get the administrator to look at a post. In addition, the posts had some sandboxing thing going on; I wasn’t able to see anyone else’s posts, and asw wasn’t able to see any of the posts I made.

I tried to get an XSS exploit working for a while, and eventually gave up and moved on to some other challenges.

It turns out that this XSS was a complete red herring. At some point Sunday morning, when I was playing around submitting strange inputs for various parameters, I stumbled across the following page:


Somehow there was a Local File Inclusion in the post parameter of post.wtf. And more importantly, it seems to have printed out all of the server code! (And wow, it was actually written in bash).

At this point, Anderson was awake, so we started looking through the server code together. We found out that interestingly, wtf.sh essentially uses the Linux filesystem in place of a database (hence the LFI in post.wtf). Posts are stored in the “/posts” directory, users are stored in the “/users” directory, etc.

After looking through the code a bit, we found the part where users are created.

# The caller is responsible for checking that the user doesn't exist already calling this
function create_user {
    local username=$1;
    local password=$2;
    local hashed_pass=$(hash_password ${password});
    local hashed_username=$(hash_username "${username}");
    local token=$(generate_token);

    mkdir users 2> /dev/null; # make sure users directory exists
    touch users/.nolist; # make sure that the users dir can't be listed
    touch users/.noread; # don't allow reading of user files directly

    mkdir users_lookup 2> /dev/null; # make sure the username -> userid lookup directory exists
    touch users_lookup/.nolist; # don't let it be listed

    local user_id=$(basename $(mktemp users/XXXXX));

    # user files look like:
    #   username
    #   hashed_pass
    #   token
    echo "${username}" > "users/${user_id}";
    echo "${hashed_pass}" >> "users/${user_id}";
    echo "${token}" >> "users/${user_id}";

    mkdir "users_lookup/${hashed_username}" 2> /dev/null;
    touch "users_lookup/${hashed_username}/.nolist"; # lookup dir for this user can't be readable
    touch "users_lookup/${hashed_username}/.noread"; # don't allow reading the lookup dir
    touch "users_lookup/${hashed_username}/posts"; # lookup for posts this user has participated in
    echo "${user_id}" > "users_lookup/${hashed_username}/userid"; # create reverse lookup

    echo ${user_id};

Huh, so tokens are also stored in the “users/” directory, and it looks like they never expire. I wonder if the Local File Inclusion in post.wtf can also view this directory…


There’s the admin token (we also have the hashed password, but that might be hard to crack)! Setting our TOKEN cookie equal to this base64-encoded string and setting our USERNAME cookie to admin, we can log in to the admin’s profile and find the flag.


We didn’t end up solving wtf.sh (2), but we did work on it a bit at the very end of the CTF, so I might as well summarize what we tried here.

Our first thought was to try to somehow inject some sort of bash command somewhere where the server would accidentally evaluate it. We found an eval() in the code that renders .wtf pages, but unfortunately it only runs on files that end in the .wtf extension, which we didn’t know how to create (it turns out you could in fact do this by exploiting some tricky code in the reply function involving Bash arrays). We found a couple other places where we thought you could inject Bash code, only to finally understand how Bash works and realize that those places were safe.

Our next thought was: well, the binaries get_flag1 and get_flag2 must exist somewhere on the file system. If we can read them out through the LFI exploit we found earlier, maybe they’ll just contain the flag. We ended up trying things like this and playing around with the LFI exploit until the end of the CTF. One annoying thing about the LFI exploit is that you can only see the contents of files, not the filenames, so you sort of had to guess where files were. We ended up downloading the contents of /usr/bin and /usr/share/bin and /etc and looked for evidence of where the flag might be.

By looking in /etc/passwd, we did notice there were two users named “flag1” and “flag2”; unfortunately, we couldn’t view any of the files in /home/flag2 or /home/flag1. We also couldn’t find any flags in any of the binaries we downloaded (although it did turn out that both get_flag1 and get_flag2 were in /usr/bin; they just didn’t contain a string of the form “flag{“). In any case, this approach was doomed to fail, since the get_flag2 binary was a setuid binaries that read the flag from /home/flag2/flag2.txt, which the ‘www’ user did not have permission to view.


One thought on “CSAW Quals 2016 – wtf.sh (1)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s