Critical Security Rule

SQL Injection
Data Flow Analysis

Known since 1996. Still #3 on OWASP Top 10. Why the oldest vulnerability class can become a zero-day any day — through data flow blind spots, bad practices, and LLM-generated code.

BD-SECURITY-TDSQL — Parasoft C/C++test
Use arrows or swipe to navigate

What is BD-SECURITY-TDSQL?

Detects possible SQL injections when tainted data reaches functions that prepare or execute SQL queries. Supported APIs: ODBC, ADO, OLE DB.

🔍 Rule Description

When an application uses unverified user data to construct SQL queries, an attacker can alter the SQL statements — taking total control of the database or executing system commands.

30
Years known (since 1996)
#3
OWASP Top 10 (2021)
CWE-89
Weakness Enumeration
New flows every day

Understanding Data Flow Analysis

Static analysis tracks data from sources through propagators to sinks. No sanitizer on the path = vulnerability.

📥 Source
User / file / network
━━▶
⚙️ Propagator
strcat / sprintf
━━▶
💀 Sink
SQLExecDirect()
Key Insight: SQL injection is a data flow problem. If any path exists from source to sink without sanitization, the vulnerability exists — no matter how well-known the pattern is.
Taint SourcesPropagatorsSinksSanitizers
Files, Pipes, Consolestrcpy, strcatSQLExecDirectSQLBindParameter
Network, Socketssprintf, snprintfSQLExecuteCustom validators
Environment VariablesAssignment, returnsmysql_queryPrepared statements
Entry-point paramsContainer insertionsPQexecParameterized queries

❌ File-Based Auth Bypass — Vulnerable

Credentials read from a file concatenated directly into SQL. Tainted data flows from fread()SQLExecDirect().

VULNERABLE
 1#include <sql.h>
 2#include <stdio.h>
 3
 4const char* reqBegin = "SELECT user_id, user_class, rights FROM users WHERE user_name = '";
 5const char* reqPass  = "' and password = '";
 6const char* reqEnd   = "'";
 7
 8void handleRequest(FILE* file) {
 9    char params[1000];
10    fread(params, 1, 1000, file);              // ← SOURCE
11    SQLCHAR req[1000];
12    strcpy(req, reqBegin);
13    strcat(req, extractUsername(params));      // ← PROPAGATION
14    strcat(req, reqPass);
15    strcat(req, extractPassword(params));      // ← PROPAGATION
16    strcat(req, reqEnd);
17    SQLExecDirect(stmtHandle, req, strlen(req));  // ← SINK 💀
18}
fread()
━▶
extractUsername()
━▶
strcat()
━▶
SQLExecDirect() 💀
Attack: Input ' or ''='WHERE user_name = '' or ''='' and password = '' or ''=''
→ All rows returned. Attacker authenticated as first user.

✅ Parameterized Query — Safe

Using SQLPrepare() + SQLBindParameter() separates data from query structure — taint is neutralized.

SAFE
 1const char* reqStr = "SELECT user_id, user_class, rights FROM users WHERE user_name = '?' and password = '?'";
 2
 3void handleRequest(FILE* file) {
 4    char params[1000];
 5    fread(params, 1, 1000, file);
 6    char* name = extractUsername(params);
 7    char* pass = extractPassword(params);
 8
 9    SQLBindParameter(stmtHandle, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, 20, 0, name, 0, &nameLen);
10    SQLBindParameter(stmtHandle, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, 20, 0, pass, 0, &passLen);
11
12    SQLPrepare(stmtHandle, reqStr, SQL_NTS);  // ← SANITIZED
13    SQLExecute(stmtHandle);                   // ← SAFE
14}
fread()
━▶
extractUsername()
━▶
SQLBindParameter() ✓
━▶
SQLExecute() ✓
Why it works: Prepared statements send the query structure and data separately to the database engine. User input can never alter the SQL grammar — the injection is structurally impossible.

❌ The "Safe Wrapper" Illusion

A trusted db_fetch() helper looks safe — but taint flows through 4 functions across 2 files. A new caller creates a zero-day without changing existing code.

db_helpers.c — VULNERABLE
1// Developer assumes callers will sanitize — they don't.
2int db_fetch(const char* table, const char* where_clause,
3             SQLCHAR* result, SQLLEN maxlen) {
4    SQLCHAR query[2048];
5    snprintf((char*)query, sizeof(query),
6             "SELECT * FROM %s WHERE %s", table, where_clause);  // ← taint in
7    SQLExecDirect(g_stmt, query, SQL_NTS);                    // ← SINK 💀
8    // ...
9}
request_handler.c — VULNERABLE
 1void handle_product_lookup(int sock) {
 2    char buf[4096];
 3    recv(sock, buf, sizeof(buf)-1, 0);                  // ← SOURCE
 4    buf[sizeof(buf)-1] = '\0';
 5    char* pid = parse_json_field(buf, "product_id");    // ← taint extracted
 6    char* clause = build_filter(pid);                    // ← taint wrapped
 7    SQLCHAR result[1024];
 8    db_fetch("products", clause, result, sizeof(result));// ← reaches sink 💀
 9}
recv() 📡
━▶
parse_json_field()
━▶
build_filter()
━▶
db_fetch()
━▶
SQLExecDirect() 💀

✅ Taint Trace → Parameterized Fix

StepVariableStatusLocation
1bufTAINTEDrequest_handler.c:3
2pidTAINTEDrequest_handler.c:5
3clauseTAINTEDrequest_handler.c:6
4queryTAINTEDdb_helpers.c:6
5SQLExecDirectSINK 💀db_helpers.c:7
db_helpers.c — SAFE
1int db_fetch_by_id(const char* table, const char* column,
2                    const char* value, SQLCHAR* result, SQLLEN maxlen) {
3    SQLCHAR query[2048];
4    snprintf((char*)query, sizeof(query),
5             "SELECT * FROM %s WHERE %s = ?", table, column);
6    SQLPrepare(g_stmt, query, SQL_NTS);
7    SQLLEN len = SQL_NTS;
8    SQLBindParameter(g_stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR,
9                       SQL_CHAR, 256, 0, (SQLPOINTER)value, 0, &len);
A    SQLExecute(g_stmt);  // ← taint neutralized ✓
B    // ...
C}
Zero-Day Insight: A single new caller of db_fetch() that passes network data creates a new flow path — a new zero-day — even if db_helpers.c hasn't changed. The vulnerability emerges from relationships between components.

❌ AI Copilot Zero-Day Factory

💬 "Write a C function that searches products by name from a CGI query string."

AI-GENERATED CODE
 1/* search_products() — AI-generated, developer approved "looks good" */
 2char* get_cgi_param(const char* name) {
 3    const char* qs = getenv("QUERY_STRING");  // ← SOURCE: attacker-controlled
 4    // ... parse and extract param ...
 5    return url_decode(val);                  // ← URL decode ≠ sanitization!
 6}
 7
 8void search_products(void) {
 9    char* term = get_cgi_param("q");           // ← tainted
10    if (!term) return;
11
12    // LLM added a length check — devs think this is "validation"
13    if (strlen(term) > 200) { free(term); return; }
14
15    SQLCHAR query[1024];
16    snprintf((char*)query, sizeof(query),
17             "SELECT id,name,price FROM products WHERE name LIKE '%%%s%%'",
18             term);                                // ← taint injected into LIKE
19    SQLExecDirect(g_stmt, query, SQL_NTS);   // ← SINK 💀
20    free(term);
21}
getenv() 🌐
━▶
url_decode()
━▶
strlen() check
NOT a sanitizer!
━▶
snprintf()
━▶
SQLExecDirect() 💀
Attack: ?q=' UNION SELECT username,password,'1' FROM admin_users --
Full database exfiltration. strlen + url_decode = false sense of security.

🧠 Why the LLM Got It Wrong

What the LLM DidWhy It Seems SafeWhy It's Not
url_decode()Looks like input processingDecodes %27 back to 'expands attack surface
strlen() checkAppears to limit input' UNION SELECT…-- is only 30 chars. Length ≠ safety
Bounded snprintfPrevents buffer overflowBuffer safety ≠ SQL safety
Nice commentsGives reviewer confidenceComments don't execute. Data flow is what matters
SAFE — Parameterized LIKE
1void search_products(void) {
2    char* term = get_cgi_param("q");
3    if (!term) return;
4    char pattern[256];
5    snprintf(pattern, sizeof(pattern), "%%%s%%", term);
6    const char* sql = "SELECT id,name,price FROM products WHERE name LIKE ?";
7    SQLPrepare(g_stmt, (SQLCHAR*)sql, SQL_NTS);
8    SQLLEN len = SQL_NTS;
9    SQLBindParameter(g_stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR,
A                       SQL_CHAR, 256, 0, pattern, 0, &len);  // ← taint neutralized
B    SQLExecute(g_stmt);
C    free(term);
D}
🤖 The LLM Blind Spot: LLMs generate code from statistical patterns. They've seen millions of sprintf + SQL snippets — including vulnerable ones. They cannot reason about taint propagation across functions. They produce code that looks correct but is exploitable.

Known Since 1996 — Zero-Day Any Day

SQL injection isn't a single vulnerability — it's an infinite class of data flow problems.

1996
SQL injection first described in Phrack magazine
2008
Heartland Payment Systems — 130M credit cards via SQLi
2011
Sony PSN — 77M accounts compromised
2017
Equifax — 147M records (began with injection)
2023
MOVEit Transfer — mass SQLi exploitation
2024–26
LLM copilots generate vulnerable SQL at scale — new zero-days daily

1. Infinite Flow Paths

Every new function creates new taint paths. Safe yesterday → vulnerable after one commit.

2. Human Assumptions

"The wrapper is safe." "It was validated elsewhere." Only data flow analysis can verify.

3. LLM Generation

AI copilots produce well-commented code with classic injection patterns. Can't reason about taint.

4. Cross-Module Depth

Taint traverses 10+ calls across 5+ files. Only automated inter-procedural analysis can find them.

Rule Parameters

Customize how BD-SECURITY-TDSQL detects taint sources, sanitizers, and data flow paths.

📥 Sources of Tainted Data

  • Files & Pipes
  • Stream-oriented APIs (std::istream, CArchive, CFile)
  • Low-level input (Windows API, POSIX)
  • Console & Environment variables
  • Entry-point method parameters
  • Network & Sockets
  • Random number generators

🛡️ Validating Functions

  • Type/namespace — declaring type (* for any)
  • Function name — sanitizer name (wildcards)
  • Subclass defs — apply to overrides in derived classes
  • 'this' validated — cleans calling object
  • Returns validated — cleans return value
  • Validated params — 1-based indexes or *

🔢 Numerical Data

When enabled, numerical data is never considered tainted — reduces false positives for numeric IDs.

Standards & Compliance

BD-SECURITY-TDSQL maps to the following industry standards.

OWASP Top 10 — 2021
A03:2021 — Injection
owasp.org → A03:2021
OWASP API Security — 2023
API10 — Unsafe Consumption of APIs
owasp.org → API10
CWE-89
SQL Injection
cwe.mitre.org → CWE-89
CWE-20
Improper Input Validation
cwe.mitre.org → CWE-20
MISRA C:2025
Dir 4.14 — Validate external data
MISRA C:2012 Amd 1
Dir 4.14 — Security guidelines
SEI CERT C
STR02-C — Sanitize data to subsystems
wiki.sei.cmu.edu → STR02-C
DISA STIG
APSC-DV-002540