More SQLi

Published: May 6, 2023

Description

Can you find the flag on this website.

Solution

The SQL Injection for CTF guide explains the multi-step UNION extraction technique used here: column count discovery, table enumeration, and data extraction.
  1. Step 1Initial exploration
    Let's start by trying "admin" for both the username and password to see what happens.
    Initial login attempt with admin credentials
    Looking at the error, we can see the SQL query being executed. Notice two important things: the password field comes first in the query, and it uses single quotes (not double quotes). This tells us where to inject our payload.
    For SQL injection, common comment characters are # and --.
    SELECT id FROM users WHERE password = 'admin' AND username = 'admin'
    Learn more

    SQL injection (SQLi)occurs when user-supplied input is concatenated directly into a SQL query without sanitization or parameterization. The database engine cannot distinguish between the developer's intended query structure and the attacker's injected SQL code, so it executes both together.

    The error message here is invaluable: it reveals the exact query template. Knowing that the password field appears first and uses single quotes tells you precisely where to inject and which quote character to close. Verbose error messages - which reveal internal query structure - are themselves a security vulnerability (CWE-209: Information Exposure Through an Error Message) that should be disabled in production.

    The two most common SQL comment styles are -- (ANSI standard, supported by PostgreSQL, SQLite, SQL Server) and # (MySQL-specific). Knowing which database backend is running helps you choose the right syntax. Stack traces and error messages often reveal the database type.

  2. Step 2Bypassing the login
    Now let's use a classic SQL injection payload in the password field. The first single quote closes the password string, OR 1=1 is always true, and the -- comment makes everything after it irrelevant.
    This transforms the SQL query into:
    Successfully bypassed login showing database interface
    Success! We're in and now see a database interface with another input field, which means more SQL injection is needed.
    ' OR 1=1 - 
    SELECT id FROM users WHERE password = '' OR 1=1 - ' AND username = 'does not matter'
    Learn more

    The payload ' OR 1=1 -- works by exploiting the query structure. The opening single quote closes the string literal that started with the password field. OR 1=1 adds a condition that is always true, so the WHERE clause evaluates to true for every row. The -- comments out the rest of the original query (the username check), making it irrelevant. The result: the query returns at least one row, and the application logs you in.

    This is one of the most well-known attack patterns in web security - it appears in OWASP's Top 10 as "Injection." The definitive fix is parameterized queries (also called prepared statements), where SQL structure and user data are sent to the database separately: SELECT id FROM users WHERE password = ? AND username = ?. The ? placeholders are filled by the database driver, which treats the values as data - never as SQL code - regardless of what they contain.

  3. Step 3Finding the number of columns
    First, we need to figure out how many columns the query returns. We'll use a UNION SELECT with null values.
    ' UNION SELECT null,null,null;--
    Before:
    Before finding column count query
    After:
    After finding column count - three columns confirmed
    Perfect! The query works with three null values, which means we're dealing with three columns.
    Learn more

    UNION-based SQL injection appends a second SELECT statement to the original query using the SQL UNION operator. For UNION to succeed, both SELECT statements must return the same number of columns with compatible data types. The standard technique to discover the column count is to try UNION SELECT null,null,... with increasing numbers of nulls until the query succeeds instead of erroring. null is used because it is compatible with every data type.

    Once the column count is confirmed, you identify which columns are displayed to the user (they must be of string type) by replacing nulls one at a time with a visible string like 'a'. The position where 'a' appears in the output is the column you will use to exfiltrate data in subsequent queries.

    This enumeration process is tedious to do manually. Tools like sqlmap automate the entire workflow - detection, column counting, type detection, and data extraction - but understanding the manual process is essential for CTF challenges where sqlmap may be blocked or too noisy.

  4. Step 4Enumerating the database schema
    Since this is likely SQLite (common in CTF challenges), we can query sqlite_master, an internal table that contains schema information.
    Database schema showing more_table with flag column
    Excellent! From the output, we can see there's a table called more_table with a column named flag. That's exactly what we need!
    ' UNION SELECT sql,null,null FROM sqlite_master;--
    Learn more

    Every database engine exposes metadata about its own structure through special tables or views. In SQLite, sqlite_master contains one row per database object (table, index, view, trigger) with the original CREATE statement in the sql column. This lets an attacker enumerate every table name and column definition in a single query.

    Equivalent metadata tables in other databases: MySQL/MariaDB uses information_schema.tables and information_schema.columns; PostgreSQL uses pg_tables and information_schema; Microsoft SQL Server uses sys.tables and sys.columns. Knowing which system you're targeting determines which metadata query to use.

    This schema enumeration step is critical in real-world SQLi attacks - it tells the attacker exactly what data exists before they start extracting it. Modern web application firewalls (WAFs) often block queries containing information_schema or sqlite_master, which is why understanding multiple bypass techniques is valuable.

  5. Step 5Extracting the flag
    Now we just need to select the flag from more_table. You can put the flag in any column position.
    Flag successfully extracted from database
    And there's the flag!
    ' UNION SELECT null,null,flag FROM more_table;--
    ' UNION SELECT flag,null,null FROM more_table;--
    Learn more

    With the table and column names confirmed, the final UNION SELECT directly reads the target data. Placing the interesting column in position 1, 2, or 3 (whichever appears in the output) lets you retrieve it. If the table has multiple rows, all of them appear in the result set - a UNION returns all matching rows from both SELECT statements.

    In real-world SQL injection, the "flag" is whatever sensitive data the attacker is after: password hashes, credit card numbers, session tokens, private keys, or PII. The attack chain is always the same: bypass authentication, enumerate schema, extract target columns. The entire process takes minutes with sqlmap, or an hour manually - which is why parameterized queries are the only reliable defense.

    For learning, try practicing SQLi on intentionally vulnerable applications like DVWA (Damn Vulnerable Web Application), WebGoat, or HackTheBox web challenges. Understanding both the attack and the defense makes you a better developer and security tester.

Flag

picoCTF{G3tting_5QL_1nJ3c7I0N_l1k3_y0u_sh0...}

Want more picoCTF 2023 writeups?

Useful tools for Web Exploitation

Related reading

What to try next