logo svg
logo

November 4, 2026

Updated: November 4, 2026

SQL Injection (SQLi): How It Works, Examples and Prevention

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

Mohammed Khalil

Featured Image

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.

What Is SQL Injection?

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.

What causes SQL injection?

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.

How SQL Injection Works: A Lab-Safe Example

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.

text
# 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:

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.

The same login input changes a concatenated SQL query but stays a plain value in a parameterized query

The fix: a parameterized query

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.

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

Types of SQL Injection

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.

TypeHow data comes backTypical signal in testingWhy it matters
In-band, error-basedIn the same response, inside a database error messageDatabase error text appears in the pageError details reveal table and column names
In-band, UNION-basedIn the same response, as extra result rowsExtra rows or columns appear in resultsData from other tables is shown directly
Blind, boolean-basedInferred from page contentThe page differs for true and false conditionsWorks even when no data or errors are shown
Blind, time-basedInferred from response timeResponses slow down only when a condition holdsWorks even when every page looks identical
Out-of-bandThrough a second channel such as DNS or HTTPThe database server contacts an outside hostBypasses an application that shows nothing
Second-orderStored first, triggered later by a different queryA saved value breaks a later query elsewhereEscapes tests that only check the input form
Six types of SQL injection with how data comes back, what a tester observes and the key defense for each

In-band SQL injection

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

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

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

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.

text
# 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")  # vulnerable

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

What Attackers Can Do With SQL Injection

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.

Is SQL Injection Still a Threat in 2026?

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.

Where SQL Injection Sits in the OWASP Top 10:2025

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.

Real-World SQL Injection Examples

The cases below are drawn from court records, regulators, CISA and the CVE record. Each shows a different side of the same mistake.

YearTargetType or pathWhat happenedLesson
2009Heartland Payment Systems, 7-Eleven, HannafordSQL injection against web-facing systemsUS indictment of August 17, 2009 describes more than 130 million card numbers stolenOld bug, huge blast radius
2015TalkTalk (UK)SQL injection on three vulnerable web pages of legacy systemsUK regulator fined the company a record 400,000 pounds on October 5, 2016; nearly 157,000 customers affectedInherited code is still your code
2023Progress MOVEit Transfer, CVE-2023-34362Unauthenticated SQL injection, then web shellCL0P exploitation from May 27, 2023; KEV added June 2, 2023; CVSS 9.8Mass exploitation of one internet-facing app
2024Fortinet FortiClient EMS, CVE-2023-48788SQL injection leading to code executionPublished March 12, 2024; KEV added March 25, 2024; known ransomware useManagement servers are high-value targets
2024Ivanti Endpoint Manager, CVE-2024-29824Unauthenticated SQL injection leading to code executionPublished May 31, 2024; KEV added October 2, 2024Internal-network exposure is not safety
2025Fortinet FortiWeb, CVE-2025-25257Unauthenticated SQL injection in a web application firewallPublished July 17, 2025; KEV added July 18, 2025The WAF can have the bug itself
2026LiteLLM AI gateway, CVE-2026-42208Key value mixed into query text in the API key checkPublished May 8, 2026; KEV added the same dayAI infrastructure repeats old mistakes
2026Drupal core, CVE-2026-9082SQL injection through the database abstraction APIPublished May 20, 2026; KEV added May 22, 2026A 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.

What the cases teach

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.

How Testers Find SQL Injection

Finding SQL injection is mostly careful observation, not exotic payloads. A typical manual workflow in an authorized test looks like this:

  1. Map every input that might reach a query. That includes form fields, URL and path parameters, JSON and XML bodies, cookies, HTTP headers, file names and values stored for later use. APIs and admin features often hide the least-tested queries.
  2. Probe one parameter at a time. OWASP's testing guide stresses changing only one variable while keeping the others constant. Testers submit characters that have meaning in SQL, such as a single quote, and look for errors, broken pages or changed results.
  3. Confirm with logic, not guesswork. A tester compares inputs that should produce the same result if the value reaches SQL, and inputs that should produce opposite results. Consistent differences point to injection; a one-off error does not.
  4. Use blind checks only when needed, and gently. When nothing visible changes, a tester can use timing or an out-of-band callback to a host the tester controls, within the agreed scope, stopping as soon as the behavior is proven. Extracting real customer data is not part of proof.
  5. Read the code when it is available. A code review that searches for SQL built with string concatenation, format strings or template literals, and for raw-query features in the ORM, often finds bugs that black-box testing misses. Our comparison of manual and automated code review covers when each approach fits.

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.

A lesson from DeepStrike's own research: verify the classification

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.

How to Prevent SQL Injection

Preventing SQL injection is mainly about how queries are built. Use these controls together, roughly in this order of importance.

  1. Use parameterized queries for every value. Prepared statements with bound parameters are the primary defense in OWASP's guidance. Apply them to every query, including internal tools, admin pages, background jobs and queries that use data read from your own database.
  2. Allowlist anything that cannot be a parameter. Parameters hold values, not table names, column names or keywords such as ASC and DESC. Map user choices to a small set of fixed, application-owned values and reject anything else.
  3. Use your ORM safely, and know its escape hatches. An ORM parameterizes normal queries, but most offer raw SQL features. See the section below.
  4. Bind arguments in stored procedures, and inside them. OWASP warns that stored procedures are not automatically safe: binding the call does not protect dynamic SQL built inside the procedure.
  5. Run the application with least-privilege database accounts. Give each application its own account with only the rights it needs, never DBA or admin rights. Use views to expose only the needed columns, and block outbound network access from the database server.
  6. Do not rely on escaping or blocklists. OWASP rates escaping all inputs as strongly discouraged as a primary defense because it depends on the SQL dialect and configuration.
  7. Validate input as a secondary check. Type, length and format checks reduce attack surface and catch bugs early, but OWASP is clear that validation does not make a value safe to concatenate.
  8. Show generic error pages. Log database errors on the server and show users a generic message. This removes the easiest form of in-band injection and makes probing harder.
  9. Treat a WAF as a compensating control. See the WAF section below.
  10. Build testing into delivery. Add SAST rules that flag string-built SQL, run DAST against staging, and schedule manual penetration tests and retests for high-risk releases. Our secure SDLC guide shows where each check fits.

The dynamic sort order example below shows control 2 in practice:

text
# 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.

ORMs and their pitfalls

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:

text
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 in practice

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:

text
-- 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 schemas

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

What a WAF can and cannot do

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.

ControlWhat it stopsWhat it does not stop
Parameterized queriesValues changing SQL syntax, the root causeDynamic table or column names; authorization flaws
Allowlisted structureInjection through sort, filter and column choicesValues, which still need binding
Safe ORM useMost everyday query bugsRaw SQL escape hatches and framework bugs
Least-privilege accountsAccess beyond what the account needs; OS takeover in many setupsData the account can already read or change
Generic error pagesEasy error-based injection and schema leaksBlind, out-of-band and second-order injection
WAFCommon known attack patterns; known CVEs during patchingEncoded, blind, logic-specific and internal traffic
Testing and retestingBugs the other layers missedCode that is never in scope
Six layers of SQL injection prevention, from parameterized queries to retesting, with what each layer stops and misses

SQL Injection vs XSS vs Other Injection

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.

VulnerabilityInterpreterMain targetPrimary fix
SQL injectionRelational databaseStored data and the database serverParameterized queries
NoSQL injectionDocument database query engineStored dataTyped queries; reject operator objects in input
Cross-site scripting (XSS)The user's browserUsers' sessions and actionsContextual output encoding
Server-side template injectionTemplate engine on the serverThe application serverNever build templates from user input
OS command injectionSystem shellThe host operating systemAvoid 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.

Where SQL Injection Fits in a Web Application Penetration Test

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.

Frequently Asked Questions

What is SQL injection in simple terms?

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.

What is an example of SQL injection?

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.

Is SQL injection still a threat?

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.

What is blind SQL injection?

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.

How do you prevent SQL injection?

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.

Does an ORM prevent SQL injection?

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.

Which tool is used for SQL injection testing?

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.

Is SQL injection illegal?

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.

Next Step: Find the SQL Injection Before Someone Else Does

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.

About The Author

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.

Sources and References

background
Let's hack you before real hackers do

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

Contact Us