Reproduce an SQL injection, neutralize it with prepared statements, and apply least privilege.
Open this lesson in KodokonAn SQL injection occurs the moment untrusted data is concatenated into the text of a query. The attacker supplies a value that closes the string and hijacks the logic. Let's reproduce the attack on a users table: directly concatenating the input ada' OR '1'='1 turns a precise filter into an always-true condition.
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
is_admin INTEGER NOT NULL DEFAULT 0
);
INSERT INTO users (id, name, is_admin)
VALUES (1, 'ada', 0), (2, 'root', 1);
-- Dangerous concatenation (NEVER do this)
-- input = ada' OR '1'='1
SELECT * FROM users
WHERE name = 'ada' OR '1'='1';The payload works because the quote prematurely closes the literal 'ada', then OR '1'='1' adds a universally true condition. A variant appends -- to comment out the rest of the original query. The lesson: as long as the data and the code share the same string, no manual escaping is truly reliable.
-- Positional placeholder
SELECT * FROM users WHERE name = ?;
-- Named placeholder
SELECT * FROM users WHERE name = :name;The remedy is the prepared statement: the driver first compiles the structure of the query, then binds the values separately. The bound data is never parsed as SQL, only compared. As a bonus, the compiled structure is reusable: binding several values in succession avoids recompiling the plan on every call.
-- The bound value stays data, not SQL
SELECT * FROM users
WHERE name = :name;
-- :name = ada' OR '1'='1
-- -> 0 rows: no user
-- is literally named that' OR '1'='1 succeeds at injection because it...? parameter?:name