SQL injection happens when an application takes user input — a search box, a login field, a URL parameter — and drops it directly into a SQL query without neutralizing the characters that give SQL its syntax. A single apostrophe can be enough. Type ' OR '1'='1 into a login field built on a vulnerable query, and the database stops checking for a matching password and returns every row in the table instead. This flaw is tracked as CWE-89, and it has been sitting inside the OWASP Top 10 since the list's first edition in 2003. In 2021, OWASP's Injection category (A03) still showed a 94% maximum incidence rate across the applications tested for the report, with 274,000 occurrences logged across more than 500,000 scanned apps. The mechanics are simple enough to explain in one paragraph — which is exactly why the flaw keeps reappearing in code written by experienced engineers, two decades after it was first named.
What Is SQL Injection, Technically?
SQL injection is the insertion of attacker-controlled input into a SQL statement in a way that changes the statement's logic instead of just supplying a value. A typical vulnerable pattern looks like this in a Node.js/Express app:
const query = `SELECT * FROM users WHERE username = '${req.body.username}' AND password = '${req.body.password}'`;
db.query(query);
If a user submits admin' -- as the username, the resulting query becomes SELECT * FROM users WHERE username = 'admin' --' AND password = ''. The double dash comments out the rest of the line, so the password check never executes and the attacker is authenticated as admin. The root cause is string concatenation of untrusted input into executable code — the database engine has no way to distinguish "data" from "instructions" once they're merged into the same text string.
How Does an Attacker Actually Exploit a SQLi Flaw?
An attacker exploits SQL injection by sending crafted input that alters the query's structure, then observing or extracting the response to build a full attack. The process usually follows four stages: (1) fuzzing input fields with characters like ', ", ;, and -- to trigger a database error that confirms the injection point; (2) fingerprinting the database engine (MySQL, PostgreSQL, MSSQL) from error message syntax or timing behavior; (3) using UNION SELECT statements to pull data from other tables, such as ' UNION SELECT username, password FROM admin_users --; and (4) escalating to blind or time-based extraction — for example ' OR IF(1=1, SLEEP(5), 0) -- — when the app suppresses error output but still shows a measurable delay. Automated tools like sqlmap can run this entire sequence unattended, testing hundreds of payload variants against a single parameter in under a minute.
What Are the Main Types of SQL Injection?
There are five commonly classified variants, and they differ mainly in how the attacker retrieves feedback from the database. In-band injection (error-based and UNION-based) returns data directly in the HTTP response, making it the fastest to exploit. Blind boolean-based injection infers data one bit at a time by comparing true/false page behavior, such as ' AND 1=1 -- returning a normal page while ' AND 1=2 -- returns an empty one. Blind time-based injection uses response delays (SLEEP(), WAITFOR DELAY) instead of visible differences, and can still exfiltrate a full database given enough requests — often tens of thousands for a modest-sized table. Out-of-band injection exploits database functions that trigger DNS or HTTP requests to an attacker-controlled server, useful when the app suppresses both errors and timing signals. Second-order injection stores malicious input in the database first (e.g., through a profile field) and triggers the injection later when a separate, unsanitized query reads that stored value.
Why Is SQLi Still Common Given How Old It Is?
SQL injection persists because dynamic query construction remains a normal, unremarkable-looking pattern in everyday code, especially in legacy codebases, internal admin tools, and fast-shipped features that skip a security review. The NVD had assigned more than 8,700 CVEs tagged CWE-89 as of 2024, and new ones continue to land in current products, not just abandoned ones — CVE-2023-34362 in Progress Software's MOVEit Transfer, patched in June 2023, is a Java/SQL-based file transfer product actively maintained at the time it was exploited. ORMs and parameterized query libraries have reduced — but not eliminated — the problem, because developers still drop to raw SQL for complex reporting queries, dynamic sort/filter logic (ORDER BY ${column}), or performance-sensitive code paths, and each of those escape hatches reintroduces the exact string-concatenation risk the ORM was meant to prevent. Contractor-built legacy modules and third-party plugins bolted onto larger platforms are a frequent source, since they're reviewed less often than core application code.
What Real-World Breaches Started With SQL Injection?
Several of the largest breaches of the last two decades trace back to an unsanitized query parameter. The Heartland Payment Systems breach, disclosed in January 2009, began with SQL injection against a payment processing network and exposed roughly 134 million card numbers, making it one of the largest card-data breaches in US history at the time. In June 2011, LulzSec used SQL injection against Sony Pictures' website to extract more than 1 million user accounts, including plaintext passwords and personal data, from a database that reportedly had no encryption on that table. TalkTalk's October 2015 breach, which exposed personal data for roughly 157,000 UK customers, was executed through a SQL injection vulnerability in a webpage inherited from an earlier corporate acquisition; the UK ICO fined the company £400,000 for failing to patch a known flaw. Most recently, CVE-2023-34362 in MOVEit Transfer let the Cl0p ransomware group run SQL injection to deploy a web shell and exfiltrate data, ultimately affecting over 2,700 organizations and an estimated 93+ million individuals across sectors including government, healthcare, and finance.
How Do You Detect and Prevent SQL Injection?
Prevention starts with parameterized queries (prepared statements) that separate SQL logic from data at the driver level, so user input is never interpreted as executable syntax regardless of its content. The same login example fixed correctly looks like:
const query = 'SELECT * FROM users WHERE username = ? AND password = ?';
db.query(query, [req.body.username, req.body.password]);
Beyond parameterization, effective defense layers in least-privilege database accounts (an app account that can't run DROP TABLE limits blast radius even if injection succeeds), input validation against strict allowlists for values like sort columns or table names that can't be parameterized, and a web application firewall tuned to flag classic payload patterns (UNION SELECT, OR 1=1, SLEEP() as a compensating control, not a primary one. Detection at the code level relies on static analysis (SAST) tools that trace data flow from HTTP input sources to SQL sink functions, flagging any path where concatenation happens between the two without an intervening sanitization or parameterization step. Because a SAST scan alone produces a large volume of flagged sinks, many of which are never reachable from attacker-controlled input in practice, teams need a way to separate exploitable findings from theoretical ones before they can prioritize fixes.
How Safeguard Helps
Safeguard's reachability analysis traces every SQL sink flagged by static scanning back through the actual call graph to confirm whether attacker-controlled input can reach it at runtime, cutting through the noise of theoretical CWE-89 findings that dominate raw SAST output. Griffin AI reviews the surrounding code context — ORM usage, existing sanitization, framework-level escaping — to distinguish genuine injection risk from safe-by-construction patterns, so security teams aren't triaging the same false positive across dozens of similar endpoints. Safeguard's SBOM generation and ingest pipeline also tracks which database drivers and ORM versions are in use across your services, surfacing components with known injection-adjacent CVEs (like unpatched query builders) before they ship. When a reachable, exploitable SQL injection path is confirmed, Safeguard can open an auto-fix PR that converts the vulnerable string-concatenated query to a parameterized statement, giving engineering teams a reviewable, tested fix instead of a ticket that sits in the backlog.