November 4, 2026
Updated: November 4, 2026
A lab-safe example, the main SQLi types, dated CVEs from MOVEit to LiteLLM, how testers find it, and prevention with parameterized queries, safe ORM use, least privilege and a realistic view of WAFs.
Mohammed Khalil

SQL injection (SQLi) is a vulnerability that lets an attacker change the SQL queries an application sends to its database by slipping database syntax into input the application trusts. It happens when code builds a query by pasting user input into SQL text, so the database reads part of that input as commands instead of data. Attackers use it to read, change or delete data, bypass logins and sometimes take over the server. The fix is parameterized queries for every value, allowlisted query structure, least-privilege database accounts and testing.
SQL injection is an injection flaw in the layer where an application talks to a relational database such as MySQL, PostgreSQL, Microsoft SQL Server or Oracle. MITRE catalogs it as CWE-89, Improper Neutralization of Special Elements used in an SQL Command. The attacker does not need database credentials. They only need an input that ends up inside a query: a login field, a search box, a URL parameter, a JSON body, an HTTP header or a value the application saved earlier.
The OWASP SQL Injection Prevention Cheat Sheet puts the core problem in one line: SQL injection occurs when untrusted data becomes part of a query's SQL syntax. Once that happens, the database cannot tell the developer's instructions apart from the attacker's.
The root cause is almost always the same: the application builds the query as a single string and joins untrusted values into it. Filtering, escaping and input validation reduce the odds of an attack, but they leave the query's structure open to whatever the input contains.
The fix has been known for a long time. When CISA and the FBI published their Secure by Design Alert on eliminating SQL injection on March 25, 2024, they noted that MySQL introduced prepared statements, which separate commands from untrustworthy data, back in 2004. They asked software makers to run a formal review of their code for SQL injection and asked customers to check whether their vendors had done so.
The easiest way to see SQL injection is to compare two versions of the same login check. The code below is deliberately vulnerable and meant only for a local lab you own, such as a throwaway SQLite file. Never test applications you are not authorized to test.
# LAB ONLY: deliberately vulnerable login check (string concatenation)
import sqlite3
def find_user(conn, name):
query = "SELECT id, name FROM users WHERE name = '" + name + "'"
return conn.execute(query).fetchall()With a normal name such as alice, the database receives the query the developer expected. Now imagine the input is the classic textbook string ' OR '1'='1. After concatenation, the database receives this text:
SELECT id, name FROM users WHERE name = '' OR '1'='1'The first quote in the input closes the string the developer opened. Everything after it is parsed as SQL. The condition '1'='1' is always true, so the WHERE clause matches every row and the function returns every user. In a login form, that kind of change can let someone sign in without knowing a valid name, and the same pattern lets attackers pull data from other tables. The application never asked for a new condition; the attacker typed a condition.

A parameterized query, also called a prepared statement, sends the SQL structure and the data separately. The database fixes the query's shape first, then treats the bound value as a single piece of data, whatever characters it contains.
# Fixed: the ? placeholder binds name as data, never as SQL
def find_user(conn, name):
query = "SELECT id, name FROM users WHERE name = ?"
return conn.execute(query, (name,)).fetchall()With this version, ' OR '1'='1 is just an unusual name. The database looks for a user literally called that and finds none. The Python sqlite3 documentation gives the same advice: always use placeholders instead of string formatting. Every mainstream language and driver supports this, from JDBC's PreparedStatement to PHP's PDO and Node.js database clients. The OWASP Query Parameterization Cheat Sheet has examples for each.
Note what parameters do not do. OWASP points out that binding prevents a value from changing SQL syntax, but it does not decide whether the caller may see the matching record. That is an authorization question, covered in our guide to insecure direct object references (IDOR).
The OWASP Web Security Testing Guide groups SQL injection into three classes by how the attacker gets results back: in-band, inferential (blind) and out-of-band. Second-order injection is a fourth pattern that describes when the injected value fires rather than how data returns.
| Type | How data comes back | Typical signal in testing | Why it matters |
|---|---|---|---|
| In-band, error-based | In the same response, inside a database error message | Database error text appears in the page | Error details reveal table and column names |
| In-band, UNION-based | In the same response, as extra result rows | Extra rows or columns appear in results | Data from other tables is shown directly |
| Blind, boolean-based | Inferred from page content | The page differs for true and false conditions | Works even when no data or errors are shown |
| Blind, time-based | Inferred from response time | Responses slow down only when a condition holds | Works even when every page looks identical |
| Out-of-band | Through a second channel such as DNS or HTTP | The database server contacts an outside host | Bypasses an application that shows nothing |
| Second-order | Stored first, triggered later by a different query | A saved value breaks a later query elsewhere | Escapes tests that only check the input form |

In-band SQL injection is the most direct type: the attacker sends the injection and reads the result in the same HTTP response. In error-based injection, the application shows raw database errors, and those messages leak information about the schema. In UNION-based injection, the attacker appends a second SELECT whose rows are merged into the page's normal results. Both depend on the application showing database output or errors to the user, which is one reason generic error pages matter.
Blind SQL injection, which OWASP calls inferential injection, happens when the application is vulnerable but shows neither query results nor database errors. The attacker asks the database yes-or-no questions and infers the answer from what changes.
Blind injection is slower, but automated tools can extract whole tables this way. A page that "looks fine" is not evidence that a parameter is safe.
Out-of-band SQL injection uses a different channel to get data out. Some database engines have features that can make DNS lookups or HTTP requests, and an attacker can abuse them to send query results to a server they control. It needs those features to be enabled and the database host to be allowed to reach the internet, which is why blocking outbound traffic from database servers is a useful hardening step.
Second-order SQL injection happens when a value is stored safely and later used unsafely. A sign-up form might insert a username with a correct parameterized query, so testing that form finds nothing. Weeks later, a reporting job or a password-change routine reads the saved username and concatenates it into a new query.
# LAB ONLY: the INSERT is safe, the later UPDATE is not
conn.execute("INSERT INTO users (name) VALUES (?)", (name,)) # safe
(stored,) = conn.execute("SELECT name FROM users WHERE id = ?", (uid,)).fetchone()
conn.execute("UPDATE audit SET actor = '" + stored + "' WHERE id = 1") # vulnerableThe fix is the same as everywhere else: bind the stored value as a parameter. OWASP's prevention guidance explicitly applies whether the data comes from an HTTP request, another service or previously stored data. Data from your own database is still untrusted input.
The impact depends on the query, the database engine and, above all, the privileges of the database account the application uses. Realistic outcomes include:
Compared with cross-site scripting, which targets users' browsers, SQL injection targets the data store itself. OWASP describes SQL injection as low frequency but high impact for exactly that reason.
Yes. SQL injection is one of the oldest web vulnerabilities, but the data shows it is still common and still exploited.
The 2026 additions are not obscure products. They include Microsoft Configuration Manager, Drupal core, WordPress core, Cisco Secure Email Gateway and the LiteLLM AI gateway. SQL injection has followed applications into new categories, including the tools companies now put in front of their AI models.
In the OWASP Top 10:2025, SQL injection belongs to A05:2025 Injection. Injection moved down from third place in the 2021 list to fifth, but OWASP notes that the category still has the greatest number of CVEs of any category and maps 37 CWEs, including SQL, NoSQL, OS command, LDAP and ORM injection and cross-site scripting.
Two notes in the OWASP A05 entry are worth repeating because they cause real bugs. First, stored procedures can still introduce SQL injection if the procedure itself concatenates queries and data. Second, SQL structures such as table and column names cannot be escaped or parameterized, so user-supplied structure names are dangerous; OWASP calls this a common issue in report-writing software. For how A05 fits with the other nine categories, see our OWASP Top 10 guide.
The cases below are drawn from court records, regulators, CISA and the CVE record. Each shows a different side of the same mistake.
| Year | Target | Type or path | What happened | Lesson |
|---|---|---|---|---|
| 2009 | Heartland Payment Systems, 7-Eleven, Hannaford | SQL injection against web-facing systems | US indictment of August 17, 2009 describes more than 130 million card numbers stolen | Old bug, huge blast radius |
| 2015 | TalkTalk (UK) | SQL injection on three vulnerable web pages of legacy systems | UK regulator fined the company a record 400,000 pounds on October 5, 2016; nearly 157,000 customers affected | Inherited code is still your code |
| 2023 | Progress MOVEit Transfer, CVE-2023-34362 | Unauthenticated SQL injection, then web shell | CL0P exploitation from May 27, 2023; KEV added June 2, 2023; CVSS 9.8 | Mass exploitation of one internet-facing app |
| 2024 | Fortinet FortiClient EMS, CVE-2023-48788 | SQL injection leading to code execution | Published March 12, 2024; KEV added March 25, 2024; known ransomware use | Management servers are high-value targets |
| 2024 | Ivanti Endpoint Manager, CVE-2024-29824 | Unauthenticated SQL injection leading to code execution | Published May 31, 2024; KEV added October 2, 2024 | Internal-network exposure is not safety |
| 2025 | Fortinet FortiWeb, CVE-2025-25257 | Unauthenticated SQL injection in a web application firewall | Published July 17, 2025; KEV added July 18, 2025 | The WAF can have the bug itself |
| 2026 | LiteLLM AI gateway, CVE-2026-42208 | Key value mixed into query text in the API key check | Published May 8, 2026; KEV added the same day | AI infrastructure repeats old mistakes |
| 2026 | Drupal core, CVE-2026-9082 | SQL injection through the database abstraction API | Published May 20, 2026; KEV added May 22, 2026 | A safe-by-default layer can still have holes |
Sources for the table: the Department of Justice press release on the Gonzalez indictment, the BBC report on the TalkTalk fine, CISA and FBI advisory AA23-158A on the MOVEit campaign, and the NVD records for CVE-2023-34362, CVE-2023-48788, CVE-2024-29824, CVE-2025-25257, CVE-2026-42208 and CVE-2026-9082. KEV dates come from the CISA catalog.
MOVEit shows the speed of mass exploitation. According to AA23-158A, published June 7, 2023, the CL0P ransomware gang, also known as TA505, began exploiting the unknown SQL injection flaw on May 27, 2023. Internet-facing MOVEit Transfer servers were infected with a web shell called LEMURLOOT, disguised as a legitimate file, which was then used to steal data from the underlying databases. The Secure by Design Alert that followed in 2024 described the campaign as impacting thousands of organizations.
LiteLLM shows the textbook mistake in new code. The CVE record says a database query used during API key checks mixed the caller-supplied key value into the query text instead of passing it as a separate parameter. An unauthenticated request with a crafted Authorization header could reach that query, read data from the proxy's database, and potentially expose the credentials the gateway manages. Versions from 1.81.16 to before 1.83.7 were affected. That is the exact pattern from the lab example above, in a 2026 AI product.
Drupal and FortiWeb show that layers fail too. Drupal's flaw sat in its database abstraction API, the layer meant to protect developers from writing raw SQL. FortiWeb is itself a web application firewall. Neither fact means abstraction layers or WAFs are useless; it means they need patching and testing like any other code.
Finding SQL injection is mostly careful observation, not exotic payloads. A typical manual workflow in an authorized test looks like this:
Automated scanners and tools such as sqlmap, the Burp Suite scanner and ZAP are useful for breadth, but they produce false positives and miss logic-dependent and second-order cases. Human testers confirm each finding and explain its real impact.
Classification matters too. DeepStrike's research team reported CVE-2025-63724 in SVX Portal 2.7A, published on NVD on November 14, 2025. The CVE description calls it SQL injection, and CISA's enrichment maps it to CWE-89.
When we re-examined the upstream code, we found that every field name and value is escaped and quoted before it reaches the query, so the request cannot change the structure of the SQL statement. The real weakness is that an admin session, or a forged cross-site request from one, can overwrite any portal setting because there is no allowlist and no CSRF token. We updated our SVX Portal CVE-2025-63724 write-up on November 3, 2026 to say so. The textbook string ' OR '1'='1 sent in a field name there simply updates nothing.
The takeaway for testers and defenders: a CWE label is a starting point, not proof. Confirm what input can actually do before you rate it, and recommend the fix for the real flaw. Even when escaping happens to work, as it did here, parameterized queries are still the right fix because escaping is fragile and harder to audit.
Preventing SQL injection is mainly about how queries are built. Use these controls together, roughly in this order of importance.
The dynamic sort order example below shows control 2 in practice:
# Allowlist structure, bind values
SORT_COLUMNS = {"name": "name", "created": "created_at"}
DIRECTIONS = {"asc": "ASC", "desc": "DESC"}
def list_users(conn, sort, direction, team_id):
col = SORT_COLUMNS.get(sort, "name") # unknown input falls back
order = DIRECTIONS.get(direction, "ASC")
query = f"SELECT id, name FROM users WHERE team_id = ? ORDER BY {col} {order}"
return conn.execute(query, (team_id,)).fetchall()Only values from the two dictionaries can reach the ORDER BY clause, and the team ID is still bound as a parameter.
Object-relational mappers such as Hibernate, Entity Framework, Django's ORM, SQLAlchemy, Sequelize and Prisma build parameterized SQL for normal queries, which removes most SQL injection from day-to-day code. The risk returns in four places:
Here is the raw-SQL trap in SQLAlchemy and its fix:
from sqlalchemy import text
# Vulnerable: an f-string puts the value into the SQL text
session.execute(text(f"SELECT id FROM users WHERE email = '{email}'"))
# Fixed: a named bind parameter keeps the value as data
session.execute(text("SELECT id FROM users WHERE email = :email"), {"email": email})Least privilege does not prevent SQL injection, as OWASP stresses, but it decides how bad a missed bug gets. A login page needs to read a few columns, not drop tables or read the payments schema. In PostgreSQL, a narrowly scoped application role looks like this:
-- One role per application, only the rights it needs
CREATE ROLE app_web LOGIN PASSWORD 'set-from-secret-store';
GRANT SELECT, INSERT, UPDATE ON customers, orders TO app_web;
GRANT SELECT ON product_catalog_public TO app_web; -- a view, not the base table
-- No superuser, no CREATE, no DROP, no access to other schemasPair this with separate accounts for separate applications, a database service account with minimal operating system rights, and firewall rules that stop the database server from making outbound connections.
A web application firewall inspects requests and blocks those that look like attacks. It is worth having, especially to put a virtual patch in front of a known CVE while you deploy the real fix. Its limits are just as real:
Use a WAF to buy time and gain visibility, then fix the query.
| Control | What it stops | What it does not stop |
|---|---|---|
| Parameterized queries | Values changing SQL syntax, the root cause | Dynamic table or column names; authorization flaws |
| Allowlisted structure | Injection through sort, filter and column choices | Values, which still need binding |
| Safe ORM use | Most everyday query bugs | Raw SQL escape hatches and framework bugs |
| Least-privilege accounts | Access beyond what the account needs; OS takeover in many setups | Data the account can already read or change |
| Generic error pages | Easy error-based injection and schema leaks | Blind, out-of-band and second-order injection |
| WAF | Common known attack patterns; known CVEs during patching | Encoded, blind, logic-specific and internal traffic |
| Testing and retesting | Bugs the other layers missed | Code that is never in scope |

SQL injection is one member of the injection family. The pattern is always untrusted data reaching an interpreter as code; what changes is the interpreter.
| Vulnerability | Interpreter | Main target | Primary fix |
|---|---|---|---|
| SQL injection | Relational database | Stored data and the database server | Parameterized queries |
| NoSQL injection | Document database query engine | Stored data | Typed queries; reject operator objects in input |
| Cross-site scripting (XSS) | The user's browser | Users' sessions and actions | Contextual output encoding |
| Server-side template injection | Template engine on the server | The application server | Never build templates from user input |
| OS command injection | System shell | The host operating system | Avoid shells; use APIs with argument lists |
For the browser-side counterpart, read our cross-site scripting (XSS) guide. For template engines, see the server-side template injection guide, and for the wider list, our roundup of the most common web application vulnerabilities.
SQL injection is core scope in any serious web application test, alongside authorization flaws and the rest of the OWASP Top 10. A good test traces each input to the queries it reaches, covers APIs and admin functions, checks for second-order paths, confirms every finding with minimal, non-destructive proof, and gives developers the specific query to fix.
DeepStrike's web application penetration testing services combine manual testing with automation, report only vulnerabilities validated through exploitation, and include fix recommendations, re-testing and an attestation letter.
SQL injection is when an attacker types database commands into an input field and the application runs them as part of its own query. It happens because the application pastes user input directly into SQL text. Parameterized queries prevent it by keeping input as data.
The classic example is a login query built by joining strings. If someone enters ' OR '1'='1 as the username, the query's WHERE clause becomes always true, so it can return every user. With a parameterized query, the same input is treated as a literal username and matches nothing.
Yes. SQL injection ranks second in MITRE's 2025 CWE Top 25, OWASP counts more than 14,000 SQL injection CVEs, and CISA's Known Exploited Vulnerabilities catalog lists 33 CWE-89 vulnerabilities, nine of them added in 2026, as of catalog version 2026.11.04.
Blind SQL injection is SQL injection where the application shows no query results or database errors. Attackers infer data by asking true-or-false questions and watching for page differences (boolean-based) or response delays (time-based). It is slower but just as dangerous.
Use parameterized queries or prepared statements for every value, allowlist table and column names, use ORM raw-SQL features only with bound parameters, give the application a least-privilege database account, show generic error pages, and test regularly. A WAF can help but does not fix the vulnerability.
Mostly, but not completely. ORMs parameterize normal queries, yet raw SQL methods, string-built query languages such as HQL, user-chosen column names and bugs in the framework can still lead to SQL injection. Review those spots and keep the framework patched.
Testers commonly use sqlmap, the Burp Suite scanner and ZAP alongside manual testing and code review. Tools are good for breadth but miss logic-dependent and second-order cases and report false positives, so findings need human confirmation. Only test systems you are authorized to test.
Running SQL injection against a system you do not own or have written permission to test is illegal in most countries. In the United States it can violate the Computer Fraud and Abuse Act (18 U.S.C. 1030). Practice in your own lab or on purpose-built training applications, and test real systems only under a signed scope.
Start with a search of your codebase for SQL built with string concatenation, format strings or template literals, and for raw-query calls in your ORM. Replace them with bound parameters, check that every application uses its own least-privilege database account, and make sure production shows generic error pages. Then have the application tested by people who follow each input to the database rather than run a payload list.
If you want an independent check of your application, contact DeepStrike to scope a web application penetration test.
Mohammed Khalil is a Cybersecurity Architect at DeepStrike, specializing in advanced penetration testing and offensive security operations. With certifications including CISSP, OSCP, and OSWE, he has led numerous red team engagements for Fortune 500 companies, focusing on cloud security, application vulnerabilities, and adversary emulation. His work involves dissecting complex attack chains and developing resilient defense strategies for clients in the finance, healthcare, and technology sectors.

Stay secure with DeepStrike penetration testing services. Reach out for a quote or customized technical proposal today
Contact Us