ENOWARS 3 WriteUp shittr
14 July 2019 by Jonas Bushart
Shittr was a Twitter clone and part of ENOWARS 3. It provides user accounts, which can be registered using a username and password. Each user can then send messages, which can either be private or public, as well as set a status message in their profile. A user can also see and like other user’s messages. Private messages of other users are visible, but only appear as long base64-encoded strings.
Vulnerability 1: Shared Secret Key
The first step was understanding how messages are encoded in the code base. After posting a message, the user sees a long base64-encoded string.
We follow the code to see how the message is turned into a base64-encoded string.
First we identified the place where the encoding happens.
In db.sh
there is a function called create_shit
, which contains this line:
This looks already quite promising, as we have the base64-encoding in there as well as an encryption function.
We can locate the encryption and decryption routines in utils.sh
:
The enc
function also base64-encodes its output, thus our final message which we see as the user is actually double base64-encoded.
The openssl
call looks normal, so let’s see where the encryption key is coming from.
In config.sh
we spot these lines:
The code specifies the path to the keyfile and generates a new one, if it does not exist yet.
The keyfile is located at ./ro/static/enc.key
and contains 93F4E7B14FB35801BC3F460F96FA0ACE
.
Let’s try to decode the message V2dFQVhDWlpERjF0TVMwT1B3PT0=
from above into its plaintext again:
The error message stems from openssl
and searching for iv
in the codebase does not yield any useful information.
However, we find a binary named openssl
in the ro/bin
folder.
Using this binary we receive the expected string saarsec
.
The openssl
binary behaves differently depending on the length of its argv[0]
argument.
It only works, if the argument is short, but calling it with a long absolute path results in garbage output, which cannot be decoded anymore.
This weird behaviour cost us a lot of time until we understood why our decoding did not work anymore.
Unfortunately, the openssl
binary is a large stripped executable, such that we could not check why this was happening or if there are any backdoors in the binary itself.
We noticed, that the enc.key
file was not generated by the config.sh
script, but instead was installed during the installation of the service from the deb
packages.
This means, all the teams are sharing the same secret key, and we can use our secret key to decode any private message.
Exploitation
Our first exploit simply looks for all messages on a user’s profile page. If the message looks like a flag, or we can decode the base64-encoded message to a flag, then we submit it. Accessing the profile page also gives us access to the status messages each user can set.
The main difficulty in writing the exploit was that Shittr returns the HTTP status code 1337 for its content pages, but requests
does not support custom status codes and instead throws an exception.
Fortunately, account registration and login was still possible by setting allow_redirects=False
for requests
, such that it does not hit the 1337 status code, but already receives the auth
cookie.
We then download the website using curl
, which can handle the status code, by passing the cookie to a new curl
process.
The exploit consists of these steps:
- Account registration
- Account login
- Retrieve a list of all users using the
/shittrs
endpoint. - For each user, visit the profile page at
/@username
and try to decode all messages.
Patching
We patched the vulnerability by replacing our secret key with a new one, generated with the same command from above:
After a restart, all new messages in the system were encrypted with a different key.
Vulnerability 2: Admin Access
After some time into the CTF, we noticed that other teams started spamming our system with usernames of the form <RANDOM>admin<RANDOM>
.
Tracing this pattern through the codebase, we find this function:
It tests if the username contains admin
via a regex match and if the variable DEBUG
contains any value.
The username matches, as we have seen, and in the config.sh
file DEBUG=1
is set.
Being an admin does not give you access to the private messages of a user.
You only get access to the /log
endpoint.
It contains the list of all requested websites and information about account registration and login.
The interesting information in the log are lines like Session is 708423527839774631739000000000
.
These contain the secret which is used to calculate the auth
cookie and thus the user authentication.
Everyone in possession of these values can forge session cookies.
Patching
The gameserver never used any accounts with admin
in their names.
We disabled all admin functionality by replacing ADMIN=1
with ADMIN=0
in the entire code base, thus blocking the log as an information leak.
Vulnerability 3: Weak Authentication Cookies
Based the on the findings of the previous vulnerability, we analyze the session code more.
Session handling is part of the db.sh
file, with this function being the most relevant one:
This code generates a random value and uses that to generate the session ID (the auth
cookie), by calculating the md5 over parts of it.
However, the code only takes the first three digits (${rand:0:3}
).
This mean that in total there are only 1000 different auth
cookies, from md5("000\n")
to md5("999\n")
.
Exploit
The exploit for this vulnerability simply brute forces all possible values for the auth
cookie.
We do this in a loop for every team and retrieve the page with the current user’s messages at the /
endpoint.
This was a very successful exploit which gave us many points over the rest of the game.
Patching
We fixed this problem by using the full value of rand
as input to md5sum
, thus drastically increasing the entropy.
Unknowns: Including Images
There was another functionality in the Shittr service which made us suspicious, but which we could not figure out how to exploit.
While sending a message you could embed an image link.
If the image link pointed to a http(s) site and contained the string .png
, then the service would download the image and replace the URL with an img
tag.
The suspicious code can be found in db.sh
in the create_shit
function:
The code reads the first URL matching the regex (http.?)?://[\S\[\]:.png]+
and if it contains .png
, then the if-condition at the top of the while loop will be passed and the file downloaded.
The regex has multiple irregularities.
For one, it allows you to fully omit the protocol part and start with ://
.
While curl
does support URLs without a protocol part, but only if the ://
part is also missing.
As such, it did not seem possible to leverage this detail to our benefit.
Second, the character set [\S\[\]:.png]
contains a .
which is unescaped, thus allowing any character.
This makes all the other specified character unnecessary and the whole group could just be replaced with a simple .
.
Lastly, the .png
also does not need to appear at the end of the URLs.
Any URL is fine, as long as it appears somewhere, even the fragment identifier is an option.
What this feature allows us to do, is download an arbitrary file to another team’s VM. We could use it for a Denial-of-Service attack, downloading large files and filling up the disc. If we could find a way to execute them or use them instead of the included templates, we could use it as part of a remote code execution. However, we did not find any such flaw during the CTF.
Discussion
After fixing these three vulnerabilities, we fixed the flag leaks in our service. At one point, we lost three additional flags over three game rounds, but could not figure out which exploit was used against us. After the three game rounds, the exploits stopped and we did not lose any more defensive points.