SQL Injection

This page covers what SQL injection is, how attackers exploit it, the impact it can have, and how to prevent it — the A03:2021 "Injection" category of the OWASP Top 10.

What is SQL?

SQL (Structured Query Language) is the standard language for managing relational databases: querying, inserting, updating and deleting data via SELECT, INSERT, UPDATE and DELETE. A typical, safe query looks like this:

SELECT * FROM users
WHERE username = 'alice'
  AND password = 'secret123';

This retrieves the row matching a given username and password — the kind of query a login form runs to check credentials, and the same basic pattern Lake and Crowther use to introduce SQL injection in Concise Guide to Databases [1].

What is SQL Injection?

SQL injection happens when user input is concatenated directly into a SQL query as text, rather than passed to the database as data. If an attacker can control part of that input, they can change the structure of the query itself, not just the values it operates on — and the database will faithfully execute whatever it ends up being asked to run. Vulnerable code often looks deceptively ordinary:

query = "SELECT * FROM users WHERE username = '" + username + "'"
cursor.execute(query)

Nothing about this line looks alarming in isolation — the bug is entirely in what username is allowed to contain.

A Classic Authentication-Bypass Attack

Suppose an attacker enters admin' OR '1'='1 as the username. The query above becomes:

SELECT * FROM users
WHERE username = 'admin' OR '1'='1';

Because '1'='1' is always true, the WHERE clause is satisfied for every row, so the query returns every user in the table — and depending on how the application code then uses that result, the attacker may end up logged in without ever knowing a password.

A Destructive Attack

A different input, anything'; DROP TABLE users; --, produces:

SELECT * FROM users
WHERE username = 'anything'; DROP TABLE users; --';

Here the semicolon terminates the original statement and starts a second one, DROP TABLE users, which deletes the entire table; the trailing -- comments out whatever was left of the original query syntax so it doesn't cause a parse error. The first example steals access; this one destroys data outright.

Types of SQL Injection

SQL injection is usually grouped into three broad categories, depending on how the attacker actually gets information out:

  • In-band SQLi — the attacker sees the results through the same channel as the request: error-based, reading information leaked in database error messages, or union-based, using the UNION operator to append attacker-controlled data onto the legitimate query's results.
  • Blind SQLi — the attacker cannot see the data directly, but can infer it: boolean-based, from whether the application's response differs depending on a true/false condition, or time-based, from how long the response takes to arrive.
  • Out-of-band SQLi — data is exfiltrated via an entirely separate channel, such as a DNS lookup or an outbound HTTP request the database is tricked into making.

Union-Based SQL Injection

The UNION operator combines the results of two queries. If an attacker can inject their own UNION SELECT, they can pull data from an entirely different table into the results of a legitimate one:

SELECT name, price FROM products
WHERE id = '1'
UNION
SELECT username, password FROM users;--

This requires the attacker to already know (or guess) the target table and column structure, and the injected SELECT must return the same number of columns, with compatible types, as the original.

Boolean-Based Blind Injection

When the attacker cannot see query results directly, they can still extract data one bit of information at a time by asking the database true/false questions and watching how the application's behaviour changes:

SELECT * FROM users WHERE id = '1'
AND (SELECT SUBSTRING(password,1,1) FROM users WHERE username='admin') = 'a';

If the application's response differs from its response to a false condition, the first character of the admin password is 'a'; the attacker repeats this, character by character, position by position, until the whole value has been recovered.

Time-Based Blind Injection

Where even the application's response doesn't visibly change, timing can still leak the same kind of true/false answer:

SELECT * FROM users WHERE username = 'admin'
AND (SELECT CASE WHEN (1=1) THEN pg_sleep(5) ELSE 0 END);

If the response takes roughly five seconds longer than usual, the condition was true. This technique is commonly used precisely when there is no other visible output to exploit.

Impact of SQL Injection

Depending on the database, the application's own privileges, and what the attacker chooses to do, SQL injection can lead to authentication bypass (logging in as an administrator without a password), data theft (extracting an entire database's contents), data modification (altering prices, grades, or account balances), data deletion (DROP TABLE, DELETE FROM), and in more severe cases server compromise (writing files or executing commands via database features that support it) or lateral movement into other systems reachable from the compromised database server.

Real-World Incidents

Two well-documented breaches show what that impact looks like at scale. Heartland Payment Systems was breached via SQL injection against an eight-year-old web login page in late 2007/2008; the attackers went undetected for roughly six months while sniffer malware harvested card data in transit, ultimately exposing over 130 million card numbers. Heartland's own disclosed breach-related costs came to roughly $140 million, including settlements with Visa (~$60M), Mastercard (~$34.8M), and American Express (~$3.5M), plus legal fees. Yahoo Voices was compromised in July 2012 by a group calling itself "D33DS Company," who used a union-based SQL injection attack against Yahoo's database servers to expose roughly 450,000 usernames and passwords — stored, damagingly, as plaintext rather than hashed.

It's worth being precise here rather than repeating a commonly-cited but inaccurate example: British Airways' 2018 breach of roughly 244,000 customers' payment details is often lumped in with SQL injection incidents, but it wasn't one. Attackers compromised a third-party supplier's credentials and modified a JavaScript file already running on the BA site (a Modernizr script) to silently copy payment-form data to an attacker-controlled domain as customers typed it — a client-side web-skimming technique known as Magecart, entirely unrelated to how the database was queried. The distinction matters practically: the SQL injection defences later in this page would have done nothing to stop it, because the vulnerability wasn't in the database layer at all. Real security work depends on correctly identifying which attack class you're actually defending against.

Preventing SQL Injection

1. Use Parameterised Queries (Prepared Statements)

The single most effective fix is to stop building queries by string concatenation at all, and let the database driver handle the separation between code and data:

# VULNERABLE:
query = f"SELECT * FROM users WHERE username = '{username}'"

# SAFE:
query = "SELECT * FROM users WHERE username = %s"
cursor.execute(query, (username,))

With a parameterised query, the driver sends the query structure and the user-supplied value to the database separately — there is no string for an attacker's input to escape out of, because it is never treated as part of the query text in the first place. OWASP's own prevention guidance makes parameterised queries the primary defence for exactly this reason [2].

2. Use an ORM

Object-relational mappers such as SQLAlchemy, the Django ORM, or Entity Framework parameterise queries automatically as part of their normal operation:

# SQLAlchemy example
user = session.query(User).filter(User.username == username).first()

3. Input Validation

Whitelist validation — for example, rejecting a username that doesn't match [a-zA-Z0-9_]{3,20} — adds a further layer, but is not sufficient on its own. It is defence in depth, not a substitute for parameterised queries.

4. Principle of Least Privilege

The database account an application connects with should have only the permissions it actually needs — typically SELECT, INSERT and UPDATE on specific tables — and never DROP, CREATE or ALTER. If an injection vulnerability does slip through, this limits what an attacker can do with it.

5. Web Application Firewalls

Tools such as ModSecurity or Cloudflare's WAF can detect and block common injection patterns at the network edge. This is a useful additional layer, not a replacement for secure coding — a WAF can be bypassed by an unusual enough payload, whereas a genuinely parameterised query has nothing for the payload to exploit.

Detecting SQL Injection

Manual testing typically starts with entering a single quote into input fields and watching for database errors, then trying conditions like OR 1=1 and OR 1=0 to see if the application's behaviour changes; tools such as Burp Suite's Intruder can automate that process. Dedicated scanners — sqlmap for automated exploitation, OWASP ZAP, and Burp Suite more broadly — cover the same ground at scale, and are worth knowing both as an attacker's toolkit and as part of a defender's own testing regime.

SQL Injection in Different Contexts

Any user-controlled input that eventually reaches a SQL query is a potential vector, not just an obvious login form: web forms (login, search, registration), URL parameters (?id=1 OR 1=1), cookies (user_id=1; DROP TABLE users; --), HTTP headers (User-Agent: ' OR 1=1 --), and JSON or XML API payloads ({"username":"admin' OR '1'='1"}) have all been real-world injection points. The common thread is not the format of the input, but whether it is trusted without being treated as data.

Ethical Considerations

SQL injection tools are genuinely dual-use: the same techniques that let an attacker exfiltrate a database let a defender verify their own application is safe. That makes responsible disclosure and clear authorisation especially important — testing only systems you own or have explicit permission to test, and reporting vulnerabilities you find responsibly rather than exploiting them. Both the UK's Computer Misuse Act and the US Computer Fraud and Abuse Act treat unauthorised access as a criminal matter regardless of intent, which is worth keeping firmly in mind before running any of the techniques above against a system that isn't yours.

Summary

  • SQL injection lets an attacker control the structure of a SQL query via untrusted input, not just its values.
  • The three broad categories are in-band, blind, and out-of-band, distinguished by how the attacker retrieves information.
  • Prevention centres on parameterised queries and ORMs, backed by least privilege and input validation as defence in depth.
  • Detection ranges from manual probing to automated scanners and WAFs.
  • Real breaches (Heartland, Yahoo Voices) show the impact can run from data theft to outright data destruction — but not every high-profile card breach is SQL injection, and misattributing one (as with British Airways' 2018 Magecart attack) can lead to defending against the wrong threat.

References

  1. Lake, P. & Crowther, P. (2013). Concise Guide to Databases: A Practical Introduction. Springer. Chapter 12.6, "SQL Injection" — covers the same SQL-manipulation attack pattern (an OR clause that is always true) shown above, alongside privilege abuse and other database-security threats.
  2. OWASP. SQL Injection Prevention Cheat Sheet. OWASP Foundation. https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html