cybersecurity · intermediate · ~15 min · safe pentest lab

Detect Parameterized vs Concatenated SQL

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.

Challenge

Parameterized query detector

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.

Edge cases

  • Empty string, or a plain query with no placeholder → 0.
  • $ not followed by a digit, or : not followed by a name char → not a placeholder.
  • A placeholder AND a '...' literal in the same query → 0.

Example

  • is_parameterized("SELECT * FROM users WHERE id = ?") -> 1
  • is_parameterized("SELECT * FROM users WHERE name = 'alice'") -> 0

Input format

A single NUL-terminated C string query (may be NULL). It is a fixed buffer inside the harness, not a live database connection.

Output format

Return int 1 if the query is a safe parameterized query (has a placeholder and no single-quoted literal), otherwise 0.

Constraints

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].

Starter code

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;
}

Common mistakes

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.

Edge cases to handle

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).

Background lessons

Solve this exercise in the browser editor — compile and run against the test harness, no setup required.