Server-side

SQL Injection: The Complete Practical Guide

What SQL injection is, how to find and exploit it (UNION, error-based, blind, out-of-band), real impact, and how to prevent it. With free labs to practice on.

Published

SQL Injection: The Complete Practical Guide

SQL injection is what happens when the data you type gets treated as part of the database query instead of as a value inside it. One misplaced quote and a login form turns into a direct line to the database. It has been near the top of every web security list for two decades, and it is still worth learning first because the core idea, data confused for code, underpins almost every injection bug you will ever meet. Want to feel it rather than read about it? Sign up free and try it on a live target.

What is SQL injection?

Applications store their data in a database and ask for it with SQL, a language made of commands (SELECT, WHERE, UNION) and the values those commands act on ('alice', 42). The application is supposed to keep your input firmly on the value side of that line.

SQL injection is the failure of that boundary. When your input is pasted straight into the query text, you can close the value the developer expected and start writing SQL of your own. The one-sentence model: your data escapes into the query's code.

How a SQL query gets built

Picture a login that looks up a user by name and password. In the application it is assembled as a string, roughly:

query = "SELECT * FROM users WHERE username = '" + input + "'"

Send a normal username like alice and the database runs:

SELECT * FROM users WHERE username = 'alice'

Now send alice'-- as the username. Your quote closes the string early and -- comments out whatever followed:

SELECT * FROM users WHERE username = 'alice'--'

Nothing sanitized your quote, so it changed the shape of the query. That is the whole bug. Everything else in this guide is a technique for turning that foothold into stolen data or a shell.

Impact

SQL injection is rarely "read one extra row." Depending on the database and the account the app connects with, it can mean:

  • Data theft: dump every user, their password hashes, tokens, orders, and messages.
  • Authentication bypass: log in as any account, often an administrator, without a password.
  • Data tampering: change prices, balances, roles, or audit logs by injecting into writes.
  • Lateral movement: read internal tables, service credentials, and config that unlock other systems.
  • Remote code execution: on some stacks you can write a web shell, read local files, or run OS commands straight from the database.

That range, from a leaked email to a shell on the host, is why it sits at the top of the server-side checklist.

How to find SQL injection

The signal you are hunting is simple: does the response change when you break the SQL syntax?

  • Put a single quote ' into every parameter and watch for a 500, a database error, or any shift in the response.
  • Try a logically true and a logically false pair (' OR '1'='1 versus ' OR '1'='2) and see if the result set changes.
  • For numeric parameters, test 1 AND 1=1 versus 1 AND 1=2 with no quotes at all.
  • If nothing is reflected and no error shows, fall back to timing: a payload that sleeps for five seconds when a condition is true.

Test every input, not just the obvious ones: URL and form fields, JSON bodies, cookies, and headers. The injection often hides in the parameter nobody thinks of, like a sort column. The cheat sheet collects the fastest probes, and injection in unexpected contexts covers where defenses tend to drop.

The types of SQL injection

How you exploit an injection depends entirely on what the application gives back.

  • In-band returns data in the same response. UNION-based pulls data from other tables into the page, while error-based squeezes it out through database error messages.
  • Inferential (blind) returns nothing useful, so you ask yes/no questions. Boolean and time-based blind extract data one bit at a time.
  • Out-of-band is for when even timing fails: you make the database open a DNS or HTTP connection to a server you control and smuggle the data out in the request.

Where SQL injection occurs

Injectable sinks are anywhere input reaches a query:

  • WHERE clauses (the classic), and the VALUES of INSERT / UPDATE statements.
  • ORDER BY and column or table identifiers, which cannot be parameterized and so are a frequent weak point.
  • LIMIT / OFFSET on paginated endpoints.
  • HTTP headers (a User-Agent or X-Forwarded-For logged to the database), and JSON or nested parameters.

Injection in unexpected contexts walks through each of these.

A worked example

Say a product page loads with ?category=gifts. You add a quote: ?category=gifts' and the page throws a database error. Good, the input reaches a query.

You confirm with logic: ?category=gifts' OR '1'='1 returns every product, while ?category=gifts' OR '1'='2 returns none. The condition is yours to control.

Now you want data. You count the query's columns with ' ORDER BY 1-- -, ' ORDER BY 2-- -, climbing until ' ORDER BY 3-- - errors, so there are two columns. You find a text-friendly one with ' UNION SELECT NULL,'x'-- -, see x on the page, and swap it for real data. This target is PostgreSQL, so || joins the fields: ' UNION SELECT NULL,username||':'||password FROM users-- - (on MySQL you would use concat(username,':',password)). The product list now prints credentials. That is probe to confirm to extract, the loop you will run on every target.

Prevention

  • Parameterized queries (prepared statements) for every value. The database receives your input as data it can never reinterpret as SQL. This is the fix, not an add-on.
  • Allow-list identifiers that cannot be bound, like ORDER BY columns and directions. Map user input to a fixed set of known-good names, and never concatenate it.
  • Least privilege on the database account. No FILE or superuser rights, separate read and write roles, so a successful injection cannot read local files or run commands.
  • Defense in depth. Use your ORM's safe query builder and understand its raw escape hatches. Treat a WAF as a speed bump that buys time, not a fix, because filter bypasses exist for every blacklist.

Filtering and escaping input by hand is not reliable. Parameterize values, allow-list identifiers, and keep the database account boring.

Where to go next

Work through the cluster in order, or jump to the technique you need. Start with authentication bypass for the fastest win, then UNION-based extraction. When you want everything in one place, the SQL injection cheat sheet has every payload. And when you are ready to break something real, sign up free and start hacking.