Web Application Security

SQL Injection

From a single quote to full database dump - error-based, union, and blind SQLi, plus sqlmap and prevention.

Hard 30 minsqlidatabasesqlmap

SQL injection is the bug that has leaked more data than almost any other. It happens when user input is glued directly into a database query, letting an attacker rewrite what that query does - read other users' data, bypass logins, or dump the entire database. It's old, it's still everywhere, and it's a rite of passage for every web hacker.

Authorized targets only

Only practice SQLi against apps you own or are explicitly authorized to test - DVWA, OWASP Juice Shop, PortSwigger's labs, or a VM you control. Running these payloads against someone else's site is a crime.

The root cause: string concatenation

Imagine a login query built like this:

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

If the app pastes your input straight in, you control part of the code, not just the data. Supply a username of admin' -- and the query becomes:

SELECT * FROM users WHERE username = 'admin' -- ' AND password = '...';

The -- comments out the rest, so the password check vanishes. You're logged in as admin without a password.

Step 1 - find it: the single quote

The fastest probe is a single quote. If injecting ' produces a database error (or a different response), the input is reaching the query unsanitized.

kali@vr4cs: ~
 

That error is the app telling you it has SQLi.

Step 2 - error-based extraction

When errors are shown, you can make the database leak data inside the error message (e.g. MySQL extractvalue/updatexml, or CAST on Postgres). Useful, but noisy.

Step 3 - UNION-based

If the query returns rows to the page, UNION SELECT lets you append your own result set. First find the column count:

1 ORDER BY 1-- 
1 ORDER BY 2-- 
1 ORDER BY 3--    -- errors when you exceed the column count

Then pull data into the visible columns:

0 UNION SELECT username, password FROM users-- 

Match the columns

UNION requires the same number of columns and compatible types. Use NULL placeholders (e.g. UNION SELECT NULL, username, NULL FROM users) until it renders cleanly.

Step 4 - blind SQLi (no output, no errors)

Most real-world SQLi is blind: the page never shows query output or errors. You infer data one bit at a time.

  • Boolean-based: ask true/false questions and watch how the page changes.
    1 AND SUBSTRING((SELECT password FROM users LIMIT 1),1,1)='a'-- 
    Page looks normal → the guess is right; page differs → wrong.
  • Time-based: when nothing visibly changes, make the database sleep on a true condition.
    1; IF(SUBSTRING(...)='a', SLEEP(5), 0)-- 
    A 5-second delay means "true." This is exactly how Orange Tsai extracted Uber's database through an email-tracking link.

Seen in the wild · Uber

Real HackerOne breakdown

A base64-encoded parameter in an Uber marketing email was vulnerable to blind, time-based SQL injection - $4,000 and a database dump.

Step 5 - automate with sqlmap (carefully)

Once you've confirmed an injection point, sqlmap automates extraction. Feed it a saved Burp request:

kali@vr4cs: ~
 
# enumerate tables, then dump a table
sqlmap -r request.txt -D app --tables
sqlmap -r request.txt -D app -T users --dump

sqlmap is loud

sqlmap fires thousands of requests and can damage data with stacked queries. Only run it in scope, and prefer the lowest --level/--risk that works.

The fix: parameterized queries

Never concatenate. Use parameterized queries / prepared statements so input is always treated as data, never code.

# Python (psycopg / sqlite3) - the ? / %s is a bound parameter, not string formatting
cur.execute("SELECT * FROM users WHERE username = %s AND password = %s", (user, pw))
// PHP PDO
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ?");
$stmt->execute([$user]);

Defense in depth adds: least-privilege DB accounts, input allowlists where structure is known, and an ORM used correctly (no raw string interpolation).

Hands-on Lab

SQLi Login Bypass

Bypass a vulnerable login with a classic payload, then escalate to dumping the users table.

Key takeaways

  • SQLi happens when input becomes part of the query's code. The ' probe finds it fast.
  • Escalate: error-based → UNION-based → blind (boolean/time). Blind is the most common in the wild.
  • sqlmap automates extraction once you've found the injection point - but it's loud, so keep it in scope.
  • The real fix is parameterized queries, every time. Sanitization is not enough.