Skip to main content

SQLiLite picoCTF 2022 Solution

A web login form with a database vulnerability that can be exploited to bypass authentication.

Published: July 20, 2023Updated: August 13, 2026

Description

A login form backed by SQLite is vulnerable to classic SQL injection. The server constructs a query by directly concatenating user input into the SQL string without parameterization.

Inject SQL syntax into the username field to make the WHERE clause always true, bypassing authentication entirely.

Navigate to the challenge URL in your browser or use curl.

Submit a SQL injection payload in the username field. If you POST via curl, URL-encode the payload (' becomes %27, spaces become %20).

bash
curl -X POST 'http://saturn.picoctf.net:<PORT_FROM_INSTANCE>/login' --data-urlencode "username=' OR 1=1-- -" --data-urlencode "password=x"
bash
# Equivalent fully-encoded URL-form body:
bash
# username=%27%20OR%201%3D1--%20-&password=x

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
New to SQL injection? SQL Injection for CTF covers authentication bypass payloads like the one used here, plus UNION extraction and sqlmap. Use the SQL Injection Payload Generator to generate and copy SQLite-specific bypass payloads directly.
  1. Step 1Understand the vulnerable query
    Observation
    The challenge is a login form backed by SQLite with no parameterization. The server concatenates input straight into the query, so a classic tautology in the username field walks past the WHERE clause entirely.
    The server builds: SELECT * FROM users WHERE username='INPUT' AND password='...' - injecting ' OR 1=1-- makes it always true.
    Learn more

    The backend SQL query looks like:

    SELECT * FROM users WHERE username='INPUT' AND password='PASS'

    When you enter ' OR 1=1-- - as the username, the query becomes:

    SELECT * FROM users WHERE username='' OR 1=1-- - ' AND password='...'

    The single quote closes the username string literal. OR 1=1 makes the WHERE clause always true (1=1 is always true). The -- - is a SQL comment that comments out the rest of the query, including the AND password check. The result: the query returns all rows, the login succeeds as the first user (often admin).

  2. Step 2Submit the injection payload
    Observation
    The query chains the password check after the username match with AND. One payload closes the string literal, appends an always-true condition, and comments out the password check.
    Enter ' OR 1=1-- - as the username and anything as the password, then submit the login form.
    bash
    # In browser: Username: ' OR 1=1-- -   Password: anything
    bash
    curl -X POST 'http://saturn.picoctf.net:<PORT_FROM_INSTANCE>/login' --data-urlencode "username=' OR 1=1-- -" --data-urlencode "password=x"

    Expected output

    Logged in! The flag is: picoCTF{L00k5_l1k3_y0u_solv3d_it_...}
    What didn't work first

    Tried: Using curl -d instead of --data-urlencode to send the payload

    With -d, the shell interprets the single quote and curl sends a malformed body, the quote either stripped or triggering a parse error. The server receives the payload without its leading quote, so the SQL is invalid and authentication still fails. --data-urlencode percent-encodes the special characters, so the quote arrives intact and the payload parses server-side.

    Tried: Injecting admin'-- as the username to log in as a specific admin account

    This payload comments out the password check for a user named admin, which works only if that exact username exists. A different admin name, or no matching row, and the query returns nothing and the login fails. The OR-based tautology bypasses authentication whatever usernames exist, because it forces the WHERE clause true for every row.

    Learn more

    SQL injection is consistently ranked in the OWASP Top 10 as one of the most critical web application security risks. The root cause is always the same: user input is concatenated into a SQL query string instead of being passed as a bound parameter.

    The safe fix is to use parameterized queries (also called prepared statements):

    cursor.execute("SELECT * FROM users WHERE username=? AND password=?", (username, password))

    With parameterized queries, the input is treated as data, not as SQL syntax - special characters like single quotes are escaped automatically and cannot alter the query structure.

  3. Step 3Extract the flag from the response
    Observation
    After the injection the server logs you in and returns content in the response body. The flag is in that page output, with no further enumeration needed.
    The server returns the flag in the HTTP response body or page content after successful login bypass.
    Learn more

    In SQLite, the comment syntax is -- (two dashes). MySQL also supports #. The space after -- is required in some databases (PostgreSQL) and the trailing - is sometimes added to ensure the comment parses correctly. Using -- - (dash dash space dash) is a safe choice that works in most SQL dialects.

    Other classic bypasses include: admin'-- (log in as the admin user specifically), ' OR '1'='1 (without a comment, closes both sides of the string), and '/* for C-style block comments in MySQL.

    More advanced SQL injection techniques include UNION-based injection (appending a SELECT to extract data from other tables), blind injection (inferring data from true/false responses), and time-based blind injection (using SLEEP() to infer data from response timing).

Interactive tools
  • SQL Injection Payload GeneratorGenerate SQL injection payloads for auth bypass, UNION extraction, blind SQLi, NoSQL operator injection, and sqlmap commands. Supports MySQL, PostgreSQL, SQLite, and MSSQL.

Flag

Reveal flag

picoCTF{L00k5_l1k3_y0u_solv3d_it_...}

The flag prefix is picoCTF{L00k5_l1k3_y0u_solv3d_it_} followed by a per-instance hash suffix. Submit the SQL injection payload as the username; the flag appears in the page source after successful login.

Key takeaway

SQL injection happens when input is concatenated into a query as text rather than bound as a parameter. Parameterized queries separate syntax from data at the protocol level, which makes injection structurally impossible whatever the user sends. The same class hits NoSQL queries, LDAP filters, and OS command strings, so never building executable syntax from untrusted input reaches well past relational databases.

Related reading

Tools used in this challenge

Where to go next