Blog · E.25 · Technical

SQL injection explained

It's the oldest name in web security and still one of the most dangerous. When user input slips into a database query as code instead of data, an attacker can rewrite what that query does - dumping entire tables of users and passwords, logging in as anyone, or wiping the data. Here's how SQL injection actually works, the types (including the blind variants scanners miss), how to test for it, and the one fix that ends it for good.

SQL InjectionSQLiDatabaseBlind SQLiWeb AppSec
SQL injection: User Input → Query as Code · Read / Modify / Delete Data · Auth Bypass · In-band / Blind / Out-of-band · Fix = Parameterised Queries SQL injection: User Input → Query as Code · Read / Modify / Delete Data · Auth Bypass · In-band / Blind / Out-of-band · Fix = Parameterised Queries
// TL;DR

SQL injection happens when an app builds a database query with untrusted input that isn't separated from the query's code, letting an attacker inject SQL and change what the query does. It arises from concatenating user input into the SQL string. Impact: read/modify/delete data (entire user tables, credentials, PII), authentication bypass, sometimes file access or command execution - usually a full data breach, hence critical. Types: in-band (results returned), blind (inferred via boolean/time), out-of-band. Blind variants are where manual testing shines. The definitive fix: parameterised queries / prepared statements - send SQL structure and values separately so values can't become code. Filtering alone is unreliable. Detail below; part of every web app test.

// 01 How SQL injection works

Applications talk to their database in SQL - a language of queries like “select the user whose email is X.” The vulnerability appears when the app builds that query by concatenating user input directly into the SQL string. Imagine a login that constructs SELECT * FROM users WHERE email = '<input>' AND password = '<input>'. A normal user supplies an email; an attacker supplies SQL syntax - a value like ' OR '1'='1 - and the query becomes one that's always true, returning a user without the right password. The database can't tell the attacker's data from the developer's code, because they were mixed into one string. That's the whole vulnerability: user input crossing from data into the query's code. It's the same root cause as command injection, XSS and the other injection classes - a trust boundary that wasn't enforced.

// 02 What an attacker gains

SQL injection is critical because the database usually holds the crown jewels. With a working injection an attacker can: read sensitive data - entire tables of user records, credentials, and personal or financial information; modify or delete data; and bypass authentication by manipulating the login query to return a valid user without the password. Depending on the database and its configuration, they may also read or write files on the server, escalate privileges within the database, and in some cases reach command execution on the host. Because a single injection point often exposes the whole dataset, an exploitable SQLi is frequently a full data breach - the exact outcome the PDPL and breach-cost figures are about. That severity is why it remains a top-tier finding despite being decades old.

// 03 The types of SQL injection

01

In-band

The classic: results (or errors) come back in the response, so the attacker reads data directly - error-based or UNION-based.

02

Blind (boolean)

No data returned, but true/false conditions cause small response differences - extract data one answer at a time.

03

Blind (time)

Inject a conditional delay - a true condition makes the response slow - revealing answers through timing.

04

Out-of-band

Make the database contact an attacker-controlled server (e.g. DNS), proving injection and exfiltrating data.

The blind variants matter most in practice: modern apps often suppress errors and don't echo query results, so a real injection returns nothing obvious. A scanner may miss it; a human confirms it with boolean or time-based inference and extracts the data anyway.

// 04 How to test for it

Testing probes every input that could reach a query - not just obvious form fields but URL parameters, headers, cookies and JSON body values. The tester submits SQL metacharacters (a lone quote, comment sequences) and observes for errors or behaviour changes; injects boolean conditions (' AND 1=1 vs ' AND 1=2) to see differential responses; and uses time-based payloads (a conditional sleep) to confirm blind injection where nothing else shows. Confirming impact means carefully demonstrating data retrieval or auth bypass within the rules of engagement, since it touches live data. Automated tools help find obvious cases, but the blind, second-order and context-specific injections - and safely proving impact without damage - are the manual craft that a real web app test brings, per our methodology.

// 05 How to prevent it for good

Unlike many bugs, SQL injection has a definitive, complete fix: parameterised queries (prepared statements). Instead of building a SQL string with user input in it, the app sends the query structure and the user-supplied values to the database separately - the database treats the values strictly as data, so they can never be interpreted as SQL code. This removes the vulnerability at the root, and it's the primary recommended defence everywhere. A modern ORM that parameterises by default gives the same protection - provided any raw query it allows is also parameterised (the common slip). Add defence-in-depth: least-privilege database accounts (so an injection can't reach beyond what the app needs), input validation against expected formats, and avoiding dynamic query construction. What doesn't reliably work is filtering dangerous characters - attackers have too many encodings and techniques. Parameterise, and the whole class disappears. It's checked in every web app engagement and code review.

// 06 Frequently asked questions

What is SQL injection?

A vulnerability where an app builds a database query with untrusted input that isn't separated from the query's code, letting an attacker inject SQL and change what the query does. It arises when developers concatenate user input into the SQL string. Because the query runs with the app's database privileges, it can let an attacker read, modify or delete data, bypass authentication, and sometimes execute commands.

What can an attacker do with it?

Read sensitive data (whole tables of user records, credentials, PII/financial data), modify or delete data, and bypass authentication by manipulating a login query. Depending on the database, also read/write files, escalate privileges, or reach command execution. Because the database holds the most sensitive data, an exploitable SQLi frequently means a full data breach - hence critical.

What is blind SQL injection?

When the app is vulnerable but doesn't return query results or errors, so the attacker can't see data directly. Testers extract it by inference: boolean-based asks true/false questions and observes small response differences; time-based injects a conditional delay so a true condition makes the response slow. Slower but just as effective - a hallmark of skilled manual testing over automated scanning.

How do you prevent SQL injection?

Use parameterised queries (prepared statements), which send the SQL structure and user values separately so values can never be interpreted as code - the definitive fix. An ORM that parameterises by default works too, as long as any raw query is also parameterised. Add least-privilege database accounts and input validation. Filtering dangerous characters alone is unreliable, so parameterisation is the control that closes it.

// 07 Related reading

UG

Usama Gul

Founder & Penetration Testing Lead, CyberFortify

Confirms SQL injection where scanners can't — the blind, time-based and second-order cases — then prescribes the one fix that ends the whole class: parameterised queries.

Database-backed app?

One injection point can dump your whole user table. We test every input that reaches a query — including the blind cases scanners miss — and hand you the parameterised-query fix that ends it.

Scope a web app test → Web app testing →