cybersecurity · intermediate · ~15 min · safe pentest lab
Understand why prepared statements defeat SQL injection, and learn to distinguish a query that binds values through placeholders from one that concatenates them as quoted literals — recognising that a placeholder alone does not make a query safe.
SQL injection happens when a value is glued straight into query text instead of being passed as a bound parameter. A prepared statement keeps the SQL fixed and sends values separately through placeholders (?, $1, :name), so an attacker's input can never change the query's structure.
In this lab you audit query strings from a fixed in-harness buffer (never a live database). Your job: decide whether each string looks like a safe parameterized query or a dangerous concatenated one.
Implement int is_parameterized(const char *query) that returns 1 iff the query both (a) uses at least one placeholder — a ?, a $ immediately followed by a digit, or a : immediately followed by a name character (letter or underscore) — and (b) contains no single-quoted string literal ('), which is the tell-tale sign that a value was concatenated in. Otherwise return 0.
The insecure assumption to avoid: "it has a placeholder, so it must be safe." A query can contain both a placeholder and a concatenated '...' literal — that is still unsafe, so the presence of any single quote must veto the query. Deny by default: a NULL pointer returns 0 and must not be dereferenced.
0.$ not followed by a digit, or : not followed by a name char → not a placeholder.'...' literal in the same query → 0.is_parameterized("SELECT * FROM users WHERE id = ?") -> 1is_parameterized("SELECT * FROM users WHERE name = 'alice'") -> 0A single NUL-terminated C string query (may be NULL). It is a fixed buffer inside the harness, not a live database connection.
Return int 1 if the query is a safe parameterized query (has a placeholder and no single-quoted literal), otherwise 0.
C11, freestanding function; the harness supplies all includes. Do not read past the terminating NUL. Never dereference a NULL query. No dynamic allocation, no I/O, no global state. Treat the input as untrusted: bounds-check before reading query[i+1].
int is_parameterized(const char *query){
/* TODO: inspect query for parameter placeholders and reject
* concatenated single-quoted literals. Not implemented yet. */
(void)query;
return -1;
}
Returning 1 as soon as a placeholder is seen without checking for single quotes; dereferencing query[i+1] without confirming query[i] is not the terminator; forgetting the NULL check and crashing; treating ':' followed by a digit as a named placeholder; returning -1 or other non-{0,1} values.
NULL pointer (return 0, no deref); empty string (0); query with a placeholder but also a single-quoted literal (0, the quote vetoes it); '$' at the very end or followed by a non-digit (not a placeholder); ':' followed by a digit or punctuation such as "3:4" (not a placeholder); a plain query with no placeholder at all (0).
Solve this exercise in the browser editor — compile and run against the test harness, no setup required.