flawopen.com/SQL Injection/C/C++
Imagine a form that only ever expects a ticket number, like 482. SQL injection is what happens when someone types something sneaky into that box instead of a number — a trick phrase that makes the system say "show me every ticket" instead of just ticket 482, because the system never checked that what it received was actually just a number.
SQL injection happens when user-controlled input gets inserted directly into a database query's text. In C/C++, this class of bug frequently arrives paired with a second one: building the query with sprintf into a fixed-size buffer risks a buffer overflow (CWE-120) on the exact same line that risks SQL injection — two distinct vulnerability classes from one habit.
In 2015, UK telecom TalkTalk suffered a breach affecting over 150,000 customers after attackers exploited a SQL injection flaw in a legacy web page inherited through a company acquisition. The UK's data protection regulator fined TalkTalk £400,000, describing the failure as preventable and basic.
Source: UK Information Commissioner's Office enforcement notice, 2016 — see References below./* user_id from network input */
char query[256];
sprintf(query,
"SELECT * FROM users WHERE id = %s",
user_id);
sqlite3_exec(db, query, cb, 0, &errmsg);
/* value bound, never in the query text */
sqlite3_stmt *stmt;
sqlite3_prepare_v2(db,
"SELECT * FROM users WHERE id = ?",
-1, &stmt, 0);
sqlite3_bind_text(stmt, 1, user_id,
-1, SQLITE_TRANSIENT);
sqlite3_step(stmt);
sqlite3_prepare_v2 compiles the query's structure once; sqlite3_bind_text attaches the value to a placeholder afterward, entirely outside the SQL text the parser sees. sqlite3_exec has no equivalent mechanism at all — it runs a complete string as-is, so escaping is the only defense available to it, and escaping is not the same guarantee as parameter binding.
Switching away from the vulnerable pattern here isn't a matter of adding escaping to sqlite3_exec — that function only ever takes a complete SQL string. The fix requires switching to the sqlite3_prepare_v2 / sqlite3_bind_* / sqlite3_step API entirely.
Switching from sprintf to snprintf closes the memory-safety hole but does nothing about the query-structure hole — they're independent bugs from the same line of code and both need fixing.
MySQL's C API uses mysql_stmt_bind_param; PostgreSQL's libpq uses PQexecParams. The specific function names differ by library, but the underlying principle — never build the query string with the value inline — is identical across all of them.
Buffer safety and query-structure safety are separate concerns. A perfectly bounds-checked snprintf call still produces an injectable query if the value is interpolated into it.
Any value that ultimately traces back to something outside the process's own control — a file, an environment variable, another local process — counts as untrusted for this purpose, not just direct network input.
grep -rn "sprintf(" --include="*.c" --include="*.cpp" . | grep -i "select\|insert\|update\|delete"
grep -rn "sqlite3_exec(" --include="*.c" --include="*.cpp" .
No — it's safe for static SQL with no embedded user data. It becomes dangerous only when the query string it receives was built with user-controlled input.
The principle is identical — the specific bind function names differ (mysql_stmt_bind_param, PQexecParams) but each library has its own parameterized-query mechanism to use instead of string building.