Challenge Background
The challenge presents a simple employee portal athttp://target.ctf/login. You’re given no source code — just the URL and the note: “Only authorized employees may log in.”
The login form accepts a username and password. A failed attempt returns “Invalid credentials.” A successful one presumably returns a dashboard. Your goal is to extract the flag stored somewhere in the database.
Initial observation: the application appears to be running PHP (notice the .php extension in redirects) with a MySQL backend (suggested by the PHPSESSID cookie and a brief error message you trigger in the next step).
Attack Flow
1
Discover the Injection Point
Start by probing the username field with a single quote. Submit The application responds with a MySQL error:This confirms two things: the username parameter is directly interpolated into a SQL query without sanitization, and the backend is MySQL. The underlying query is almost certainly something like:You now have a confirmed injection point. Move on to authentication bypass as a proof of concept before going deeper.
' as the username with any password.2
Bypass Authentication
Use a classic OR-based payload to short-circuit the WHERE clause. Enter the following as the username:With any password, this transforms the query into:Because
'1'='1' is always true, the query returns the first row in the users table and logs you in as that user. In many CTF challenges, this is enough — the dashboard shows the flag. Here, it shows a generic employee dashboard with no flag. You need to dig into the database itself.3
Determine Column Count with UNION
Before constructing a UNION-based payload, you need to know how many columns the original SELECT returns. Inject ORDER BY clauses with increasing numbers until the query breaks:The query succeeds for 1, 2, and 3 but returns an error on 4. The original SELECT returns 3 columns.Now confirm which columns are reflected in the response by injecting NULL values:The page loads without error. Replace NULLs with string markers to find which column positions appear in the rendered HTML:The username field on the dashboard displays
col2. Column 2 is your output channel.4
Enumerate Databases and Tables
Query the The response cycles through multiple rows. You see: Using Result:
information_schema to list all databases:information_schema, mysql, performance_schema, and ctfdb. That last one is your target.Now enumerate tables inside ctfdb:If the page only renders one row at a time, use
LIMIT and OFFSET to paginate through results: LIMIT 1 OFFSET 0, LIMIT 1 OFFSET 1, and so on. Alternatively, use GROUP_CONCAT to collapse all values into a single cell: SELECT GROUP_CONCAT(table_name SEPARATOR ', ') FROM information_schema.tables WHERE table_schema='ctfdb'.GROUP_CONCAT:users, employees, flags. The flags table is exactly what you want.5
Extract the Flag
Enumerate the columns in the Result: The dashboard renders:Flag captured.
flags table:id, flag_value, solved_by. Now dump the flag:Manual vs Automated Approach
The manual walkthrough above teaches you exactly what’s happening at the SQL level. Under time pressure in a real competition, you’ll often want to confirm the injection point manually and then hand off extraction to sqlmap.Blind Boolean-Based Injection
Not every challenge reflects output directly. When the application only returns true/false responses (e.g., “Login successful” vs “Invalid credentials”), you need blind injection to extract data one bit at a time. The technique works by asking the database yes/no questions through the query’s truth value:--technique=B.
Time-Based Blind Injection
When the application returns identical responses regardless of query truth value (no visible difference), switch to time-based inference. A deliberate delay signals a “true” condition:--technique=T.
WAF Bypass Techniques
Many CTF challenges — and all real-world targets — deploy Web Application Firewalls that block obvious SQLi strings like
UNION SELECT or OR 1=1. Common bypass strategies include: using inline comments to break up keywords (UN/**/ION SEL/**/ECT), switching case (uNiOn SeLeCt), URL double-encoding (%2527 for a quote), using equivalent syntax (|| instead of OR, && instead of AND), and substituting banned functions with equivalents (MID() instead of SUBSTRING()). sqlmap’s --tamper scripts automate many of these transformations — try --tamper=space2comment,between,randomcase as a starting combination.Key Takeaways
- Always probe with a single quote first. The error message alone tells you the database type, which determines your syntax.
- Determine column count before UNION injection. Mismatched column counts cause query errors, not useful output.
information_schemais your map. Every MySQL database exposes it — schemata, tables, and columns are always enumerable this way.- Use
GROUP_CONCATto collapse multi-row results into a single output when you only have one display column. - Learn the manual technique before relying on sqlmap. When sqlmap fails or is too slow, you need to understand what it’s doing to adapt your approach.
- Blind injection is slower but just as powerful. If you can’t see output, you can still extract every byte — it just takes more requests.