Sql Map1 picoCTF 2026 Solution

Published: March 20, 2026

Description

You've been hired by a shadowy group of pentesters who love a good puzzle. Sloppy code and legacy hashing practices left a tiny, perfect doorway for an attacker. Slip through that doorway, act as a legit user and retrieve the secret flag.

Launch the challenge instance and open the web application.

Register for an account on the web application to access the search functionality.

Install sqlmap if not already available: pip install sqlmap

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
Two chained failures: a search endpoint that concatenates user input into SQL, and a ctf-player password stored as raw MD5. The SQL Injection for CTF guide covers sqlmap automation alongside manual injection. Use the SQL Injection Payload Generator to generate the sqlmap command (sqlmap tab) and manual UNION payloads (UNION Extraction tab) if sqlmap fails. The Hash Cracking guide covers the offline-recovery half.
  1. Step 1
    Find the injectable parameter
    Observation
    I noticed the web app exposed a search endpoint that constructs dynamic queries based on user input, which suggested the query parameter was a prime candidate for SQL injection testing before automating with sqlmap.
    Explore the web app for search or filter functionality. The search endpoint's query parameter is vulnerable to SQL injection.
    Learn more

    SQL injection occurs when user-supplied input is concatenated directly into a SQL query without proper sanitization or parameterization. The database engine cannot distinguish between the developer's intended SQL syntax and the attacker's injected payload, so it executes the injected commands with the full privileges of the database user running the query.

    Search and filter endpoints are particularly common injection points because they often need to construct dynamic queries based on user input (e.g., SELECT * FROM products WHERE name LIKE '%SEARCHTERM%'). Developers sometimes apply input validation to login forms but overlook search functionality, believing it to be lower risk because it only reads data. However, SQL injection can be used for data extraction (SELECT), modification (UPDATE/INSERT/DELETE), and even operating system command execution in some configurations.

    Manual SQL injection testing involves trying payloads like a single quote ('), which causes a syntax error if the input is unescaped; OR 1=1--, which makes a WHERE clause always true; and UNION SELECT statements to append additional query results. Error messages, response time differences, and behavioral changes all indicate injection vulnerability.

  2. Step 2
    Run sqlmap to dump the database
    Observation
    I noticed manual probing confirmed SQL injection on the search parameter, which suggested using sqlmap to automate fingerprinting and table dumping rather than crafting UNION payloads by hand.
    Point sqlmap at the search endpoint and let it fingerprint and dump. --batch skips the interactive prompts so you don't have to babysit it.
    bash
    # Search is behind login, so pass your session cookie:
    bash
    sqlmap -u 'http://HOST:PORT/login?search=test' --cookie='session=...' --batch --tables
    bash
    sqlmap -u 'http://HOST:PORT/login?search=test' --cookie='session=...' --batch -T users --dump

    When sqlmap won't cooperate, inject by hand. sqlmap frequently fails on this challenge with critical: unable to connect to the target URL, and some networks (university/corporate egress filtering) silently drop the obviously-malicious sqlmap traffic. If that happens, run a single UNION by hand from the search box instead - or paste it through the picoCTF web shell so the request originates from inside their network rather than yours.

    The backend is SQLite and the search query returns two columns, so your UNION must select exactly two. Close the string, add the union, and comment out the trailing SQL:

    ' UNION SELECT username, password FROM users-- 

    If you don't already know the table or column names, enumerate the SQLite schema first (still two columns):

    ' UNION SELECT name, sql FROM sqlite_master WHERE type='table'-- 

    Get the column count wrong (one column, three columns) and the query errors out instead of returning rows - that mismatch is itself the signal you're on the right track. The dumped row gives you a username and its MD5 hash; crack that next.

    What didn't work first

    Tried: Running sqlmap without the --cookie flag and expecting it to authenticate and reach the search endpoint.

    The search endpoint is behind login, so sqlmap without a valid session cookie receives a redirect to the login page and probes that page instead. It reports the login form as non-injectable or fails entirely with 'unable to connect to the target URL'. Pass your session cookie with --cookie='session=...' captured from browser dev tools after logging in so sqlmap actually reaches the protected search URL.

    Tried: Using a UNION SELECT with three columns (username, password, email) when the backend query only returns two columns.

    SQLite UNION requires the injected SELECT to have the same number of columns as the original query. Selecting too many or too few columns causes a 'SELECTs to the left and right of UNION do not have the same number of result columns' error with no data returned. Probe the column count first by adding NULL columns one at a time until rows appear, then confirm the correct count is two before crafting the data-extraction payload.

    Learn more

    sqlmap is an open-source automated SQL injection tool that detects and exploits injection vulnerabilities across all major database backends (MySQL, PostgreSQL, SQLite, MSSQL, Oracle). It probes the parameter using four detection strategies: boolean-based blind (compares response diffs between always-true and always-false payloads), time-based blind (injects SLEEP(5)-style delays and times the response), error-based (looks for SQL error reflections like You have an error in your SQL syntax), and UNION-based (appends UNION SELECT to extract data inline).

    The --batch flag suppresses every prompt: "Do you want sqlmap to follow this redirect?", "Skip remaining detection tests for the parameter?", the DBMS narrowing question ("It looks like the back-end DBMS is MySQL. Do you want to skip test payloads for other DBMSes?"), the risk-level prompt, and the dump confirmation. --tables lists tables; -T users --dump extracts every row from the users table.

    Note the DBMS sqlmap reports - it matters. UNION column counts, comment syntax (-- vs #), and string concatenation (|| vs CONCAT) differ between MySQL, PostgreSQL, and SQLite. If you ever drop into manual payloads, you need to know which dialect you're writing for.

  3. Step 3
    Crack the MD5 hash
    Observation
    I noticed the database dump revealed the ctf-player password stored as a 32-character hex string with no salt, which are the telltale signs of raw MD5 and suggested a lookup or wordlist attack would recover the plaintext quickly.
    The ctf-player row contains a raw MD5. Try CrackStation first (instant if it's in their lookup table), then fall back to hashcat with rockyou.txt.
    bash
    # Online: paste hash at crackstation.net
    bash
    hashcat -m 0 hash.txt rockyou.txt
    What didn't work first

    Tried: Passing the hash to hashcat with -m 1000 (NTLM mode) instead of -m 0 (raw MD5).

    NTLM and MD5 hashes are both 32 hex characters and look identical at a glance. hashcat with -m 1000 computes the NTLM digest (MD4 of the UTF-16LE password) and never matches the MD5 stored in the database, so it exhausts rockyou.txt and reports 'Exhausted' even for trivial passwords. The sqlmap dump or manual UNION output will label the column type; if it does not, run the hash through a hash-identifier tool first to confirm MD5 before choosing a mode.

    Tried: Pasting the hash into CrackStation and stopping when it returns no result, assuming the password is uncrackable.

    CrackStation only covers hashes that appear in publicly leaked breach corpora. A CTF-specific password or a common word not in their 15-billion-entry index returns empty even if hashcat with rockyou.txt cracks it in seconds. CrackStation is the fast first pass; a hashcat wordlist attack with rockyou.txt and a rule set like best64 should always be the follow-up when the lookup comes back empty.

    Learn more

    MD5 was deprecated for security use around 2004 (Wang's collision paper) and OWASP has flagged it as unfit for password storage since at least 2010. Its weaknesses for password hashing are: (1) it is extremely fast - modern GPUs compute billions of MD5s per second; (2) it has no built-in salt, so identical passwords produce identical hashes, enabling precomputed rainbow tables; (3) collisions are now trivial to construct.

    CrackStation vs hashcat - know when to use each. CrackStation is a precomputed lookup: it indexes ~15 billion known hash-plaintext pairs. If your password is in any leaked breach corpus, the lookup is instant and free. Use it first. If CrackStation comes back empty, switch to hashcat: GPU-accelerated wordlist + rules attack. -m 0 selects raw MD5; rockyou.txt (14 million passwords from the 2009 RockYou breach) is the standard starting wordlist. Add rules like -r best64.rule to mutate each candidate (capitalization, common suffixes, leetspeak).

    Modern best practice is bcrypt, Argon2id, or scrypt - adaptive functions designed to be slow and salted. They drop GPU throughput from billions/sec to thousands/sec, making cracking infeasible for any non-trivial password. See the Hash Cracking for CTF guide for a fuller hashcat workflow.

  4. Step 4
    Log in as ctf-player and read the flag
    Observation
    I noticed the hash cracker returned the plaintext password for ctf-player, which suggested logging in with those credentials directly through the web app to access the authenticated area where the flag is displayed.
    Use the cracked password to log into the ctf-player account. The flag is displayed after logging in.
    Learn more

    With a cracked password, the attacker gains full authenticated access to the admin account - the same access a legitimate admin would have. This is called credential-based access and is often more impactful than direct exploitation because it bypasses many secondary security controls (IP allowlists, MFA in some configurations, security monitoring alerts for unusual activity patterns).

    This challenge illustrates a complete attack chain common in real penetration tests: SQL injection extracts the credential database, offline cracking recovers plaintext passwords, and those credentials enable authenticated access to administrative functions. The combination of an injection vulnerability and weak password hashing creates a two-step path to full compromise. Each vulnerability alone might be rated medium severity, but chained together they become critical.

    Defense-in-depth against this attack chain requires addressing every layer: parameterized queries (prevent SQL injection), strong password hashing with bcrypt/Argon2 (prevent credential recovery even if the database is dumped), and multi-factor authentication (prevent login even with a known password). Removing any single link in the attack chain would have stopped this exploit.

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{sql_m4p_m4st3r_...}

The flag is displayed after logging in as ctf-player, whose MD5 password hash is recovered via manual UNION injection (sqlmap typically fails on this challenge with 'unable to connect').

Key takeaway

SQL injection chains naturally with weak credential storage: injection dumps the credential table, and a fast unsalted hash (MD5, SHA-1) converts those hashes back to passwords in minutes on a GPU. Parameterized queries fix injection at the protocol level, making it structurally impossible regardless of input content, while adaptive hashing functions like bcrypt and Argon2id make offline cracking infeasible even after a database dump. Each vulnerability in this chain independently deserves a fix, because attackers only need one link to hold while defenders need every link to hold.

How to prevent this

Two independent failures chained here: injectable query and crackable hash. Either fix kills the attack.

  • Use parameterized queries everywhere. cursor.execute("SELECT * FROM products WHERE name LIKE %s", (q,)) is safe; string concatenation never is. Modern ORMs (SQLAlchemy, Prisma, Hibernate) parameterize by default; just don't reach for raw SQL.
  • Hash passwords with bcrypt, argon2id, or scrypt. MD5 / SHA-1 / SHA-256 are designed to be fast; password hashes need to be deliberately slow. A cost factor of ~250ms per hash makes GPU cracking infeasible.
  • Add MFA on admin accounts and least-privilege the database user. The web app should not have permissions to read the users table outside the auth flow, let alone UNION SELECT against it.

Related reading

Want more picoCTF 2026 writeups?

Tools used in this challenge

What to try next