SQL Injection
SQL injection happens when user-controlled input is concatenated into a SQL statement. The fix is universally known and universally simple — yet SQLi remains one of the most common high-impact vulnerabilities in production code.
The canonical example
// VULNERABLE
const q = "SELECT * FROM users WHERE email = '" + email + "'";
db.query(q);
// Attacker submits: ' OR '1'='1
// Resulting query: SELECT * FROM users WHERE email = '' OR '1'='1'
The same pattern enables data exfiltration with UNION, destructive operations with ; DROP TABLE, and authentication bypasses by manipulating WHERE clauses.
Variants
- Classic / in-band. The result is returned in the response.
- Blind / boolean. The response doesn't reveal data, but its shape (e.g. true/false outputs, HTTP status codes) lets the attacker infer bits.
- Time-based blind. The attacker uses
SLEEP()or similar to make true/false visible via response latency. - Out-of-band. The injected query causes the database to emit DNS or HTTP traffic to an attacker-controlled host.
- Second-order. Malicious data is stored without execution, then triggers when re-used in a vulnerable query elsewhere.
The one true defense: parameterized queries
// SAFE — every driver in every language supports this
db.query("SELECT * FROM users WHERE email = ?", [email]);
// or
db.query("SELECT * FROM users WHERE email = $1", [email]);
Parameterized queries (also called prepared statements) send the query template and the parameters separately. The database never parses user input as SQL. This is not a defense-in-depth measure — it's the actual fix.
Things that are not defenses
- Escaping quotes by hand. Always incomplete.
- Allowlists of "bad" keywords. Always bypassable (
UNI/**/ON, hex encoding, case tricks). - Hiding error messages. Hides the bug; the attacker now uses blind techniques.
- Using an ORM. Most ORMs let you fall back to raw queries; check those carefully.
Stored procedures aren't automatically safe. A stored procedure that builds dynamic SQL from input is just as vulnerable as inline code. The protection comes from parameterized invocation, not from the procedure boundary.