// FINDING / WEB

SQL injection

CriticalWeb

Untrusted input is concatenated into a database query, so an attacker can change what the query does: read other users' rows, bypass a login, change or delete data and, on some database engines, run commands on the server. It hides in search boxes, filters, sort parameters, API bodies and HTTP headers, not only in login forms.

At a glance

Why it matters
Full read access to the database is the usual outcome: customer records, password hashes, payment and account data. Writes let an attacker create admin users or plant a backdoor, and stacked queries or database functions can lead to code execution on the host. One vulnerable parameter is enough, and exploitation is automated by widely available tools.
How to fix it
Use parameterised queries or prepared statements everywhere, including ORDER BY and LIMIT values (allow-list those). Run the application with a least-privilege database account that cannot read other schemas or write files. Treat stored procedures and ORM raw-query helpers as code that needs the same review.

Description

SQL injection happens when user-supplied input reaches a database query as part of the query's structure instead of as a value. If a query is built by concatenating strings, an input like ' OR '1'='1 or '; DROP TABLE users; -- changes what the database executes rather than what it merely looks up. The vulnerability sits in how the query is built, not in any single field, which is why it tends to show up in more than one place in an application once it exists in one.

Impact

A working SQL injection can read any data the database account can see: customer records, credentials, payment details, internal configuration. Depending on the database engine and account privileges it can also write or delete data, and in some configurations execute operating-system commands through the database (xp_cmdshell on SQL Server, COPY ... TO PROGRAM on PostgreSQL when the database account has that privilege). Because it is exploited directly against the database, it bypasses application-level access control entirely: a query with no WHERE user_id = ? clause returns every row, not just the caller's own.

How we find it

  • We test every user-controllable input that can plausibly reach a query, not only form fields: URL parameters, HTTP headers the application logs or filters on, cookie values, JSON body fields and file names.
  • Manual testing first: single and double quotes, boolean-based payloads (' OR 1=1--), and time-based payloads ('; WAITFOR DELAY '0:0:5'-- or pg_sleep(5)) to detect blind injection where no error or data difference is visible.
  • We read source where it is in scope, specifically for parameterised queries versus string concatenation, and for any dynamic SQL built from an ORM's raw-query escape hatch, which is where injection often survives in otherwise safe codebases.
  • Database error messages and response-time differences are both treated as signal; a consistent extra delay on a crafted input is as conclusive as a visible error in a blind scenario.

Fix

Use parameterised queries (prepared statements) everywhere user input reaches SQL, so the input is always bound as a value and never parsed as part of the query's structure. This is the actual fix, not a defence in depth measure: input validation and escaping reduce risk but a parameterised query removes the vulnerability class outright for that query.

// Vulnerable: user input concatenated directly into the query string
String query = "SELECT * FROM accounts WHERE username = '" + username + "'";
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(query);

// Fixed: the placeholder binds the value, it is never parsed as SQL
String query = "SELECT * FROM accounts WHERE username = ?";
PreparedStatement stmt = connection.prepareStatement(query);
stmt.setString(1, username);
ResultSet rs = stmt.executeQuery();

References

  • OWASP Top 10: A03:2021, Injection
  • CWE-89: Improper Neutralization of Special Elements used in an SQL Command
  • OWASP SQL Injection Prevention Cheat Sheet

Practise finding and fixing it in the find-and-fix lab Fix the Login Lookup: SQL Injection.