SQL injection has been publicly understood since the late 1990s. It remains on the OWASP Top 10, still appears in penetration tests, and still produces some of the largest data breaches on record. Its persistence is not a mystery: applications keep building queries by concatenating strings, and every new framework generation finds a fresh way to reintroduce the pattern.
The underlying mistake
SQL injection occurs when user-supplied input is incorporated into a database query in a way that lets the input change the query's structure rather than merely supply a value.
The root cause is always the same: the application mixes code and data in a single string. The database receives one blob of text and has no way to know which portion the developer intended as a literal value and which the user supplied.
The main varieties
- In-band / classic — results come back in the response, often via a UNION query that appends attacker-chosen columns.
- Error-based — database error messages themselves leak schema and data, which is why verbose errors must never reach production users.
- Blind boolean — no data is returned, but the page differs between true and false conditions, letting an attacker ask yes/no questions one bit at a time.
- Blind time-based — no visible difference at all, so the attacker induces a measurable delay to infer each answer. Slow, and entirely automatable.
- Out-of-band — the database is coaxed into making a DNS or HTTP request to attacker-controlled infrastructure, useful when the response channel is closed.
- Second-order — the payload is stored safely, then later concatenated into a query by a different code path. Frequently missed because injection and execution are separated in time.
Why an ORM is not immunity
Object-relational mappers parameterise by default, which prevents most injection — but they all provide an escape hatch for raw SQL, and that hatch is exactly where injection reappears.
Common failure points include raw query methods, dynamically constructed ORDER BY or table names, LIKE clauses assembled by hand, and stored procedures that concatenate internally. Placeholders also cannot be used for identifiers such as column or table names, so those must be validated against an allow-list instead.
What happens after the first injection
Reading the current table is rarely the objective. A confirmed injection is a foothold, and the escalation path is well-trodden.
- Enumerate the schema, then dump credential and token tables.
- Attempt privilege escalation inside the database itself.
- Read local files where the database permits it, harvesting configuration and secrets.
- Write files to reachable web directories to gain code execution.
- Pivot — database servers usually sit inside the network with trusted access to other systems.
The fix, in priority order
- Use parameterised queries (prepared statements) everywhere. This is the actual fix — it separates code from data at the protocol level, so input can never alter query structure.
- Allow-list any identifier that cannot be parameterised, such as sort columns and table names.
- Apply least privilege to the database account. The application rarely needs DROP, file access or administrative rights.
- Validate input by type and range as defence in depth — never as the primary control, since escaping and blocklists are historically easy to bypass.
- Disable verbose database errors in production and return generic messages.
- Treat a WAF as a detection and slowdown layer, never as the fix. It buys time; it does not remove the vulnerability.
Related from TechBiz Security
Sources & further reading
- OWASP Top 10 — A03 Injection
- OWASP SQL Injection Prevention Cheat Sheet
- PortSwigger Web Security Academy — SQL injection
- CWE-89: SQL Injection
0 comments
Leave a comment