Parameterised queries & SQL injection
Summary: Never build SQL by concatenating user input into the query string — an attacker can smuggle SQL through a value. Use parameterised queries: pass a placeholder in the SQL and the values separately, and the driver escapes them safely. The placeholder syntax differs by language; the rule is universal.
Two ways to pass values
Option A — string concatenation (dangerous):
conn.execute(f"SELECT * FROM users WHERE name = '{name}'")
Option B — parameterised query (safe):
conn.execute("SELECT * FROM users WHERE name = %s", (name,))
In option B, %s is a placeholder. You pass the actual value separately, and the database
driver substitutes it safely — escaping any special characters before it ever reaches the
SQL engine.
★ SQL injection
SQL injection is when an attacker puts SQL code inside a value (a form field, a URL param) and the app accidentally runs it as a real SQL command.
Login form — auth bypass:
# Vulnerable
username = "admin' OR '1'='1"
conn.execute(f"SELECT * FROM users WHERE username = '{username}'")
The query becomes:
SELECT * FROM users WHERE username = 'admin' OR '1'='1'
'1'='1' is always true → returns every row → the attacker is logged in as anyone.
Data destruction:
sc_id = "SC-149949'; DROP TABLE patch_sessions; --"
conn.execute(f"SELECT * FROM patch_sessions WHERE sc_id = '{sc_id}'")
The query becomes:
SELECT * FROM patch_sessions WHERE sc_id = 'SC-149949'; DROP TABLE patch_sessions; --'
The entire patch_sessions table is gone.
Why parameterised queries stop this: the driver escapes the value (a single quote '
becomes the literal ''), so the injected SQL is treated as plain text data, never as a
command.
Placeholder syntax varies — the concept is universal
| Language / driver | Placeholder |
|---|---|
| Python / psycopg | %s |
| JavaScript / node-postgres | $1, $2, $3 |
| C# / ADO.NET | @sc_id |
| Java / JDBC | ? |
The rule
Never concatenate user input directly into SQL strings. Always parameterise.