Command injection into databases remains among the most dangerous flaws in web applications. In the OWASP Top 10 taxonomy, the A03:2021 - Injection category covers SQL Injection (SQLi) and NoSQL Injection (NoSQLi), classes of vulnerability that let an attacker subvert queries and read, alter or destroy data they should never touch. This technical guide shows how they work and, above all, how to eliminate them for good.
What injection is and why A03:2021 matters
An injection vulnerability arises when user-controlled data is interpreted as code by the backend. In the case of databases, the application builds a query by concatenating raw text coming from the client. Because the database interpreter can't distinguish the developer's intent from the attacker's intent, any control character (quotes, operators, semicolons) changes the structure of the query.
OWASP consolidated these flaws into A03:2021 - Injection, which brings together SQLi, NoSQLi, OS command injection, LDAP and ORM injection. The MITRE/CWE catalog references CWE-89 (SQL Injection) and CWE-943 (Improper Neutralization of Special Elements in Data Query Logic) for NoSQL and other query languages. The root cause is always the same: a lack of separation between code and data.
The impact goes far beyond credential theft. A single successful injection can result in the full leak of a customer database, silent alteration of financial records, escalation to the operating system of the database server and, in chained scenarios, total compromise of the infrastructure. Because it is a flaw in the query construction logic, it usually passes through network firewalls and authentication: the traffic arrives legitimate at the application's door and only reveals itself as malicious when the database interprets the payload. That's why the fix has to happen in the code, not just at the perimeter.
Anatomy of a SQL Injection
Consider a login that builds the query by concatenation. The example below is classically vulnerable:
-- VULNERABLE (string concatenation)
query = "SELECT * FROM users WHERE email = '" + email + "' AND password = '" + password + "'"
If the attacker sends the value ' OR '1'='1 as the email, the resulting query becomes ... WHERE email = '' OR '1'='1' AND password = '', whose always-true clause returns the first user in the table and authenticates without a valid credential. By commenting out the rest of the query with -- or #, the attacker discards the password check entirely. From this vector, several techniques open up, grouped by how the data returns to the attacker.
In-band SQLi
The attacker uses the same channel to inject and receive the data. This includes UNION-based, where UNION SELECT is appended to exfiltrate columns from other tables, and error-based, which triggers DBMS error messages that leak structure or content (for example via extractvalue() in MySQL).
Blind SQLi
When the application returns neither data nor errors, the attacker infers information bit by bit. In boolean-based, one observes the change in behavior between AND 1=1 and AND 1=2. In time-based, a conditional delay is injected, such as SLEEP(5) in MySQL or pg_sleep(5) in PostgreSQL, and the response time is measured to deduce true/false.
Out-of-band (OOB) SQLi
When even timing isn't reliable, the database is forced to initiate an external connection (DNS or HTTP) carrying the exfiltrated data in the domain name itself. Techniques such as UTL_HTTP/DBMS_LDAP in Oracle or xp_dirtree in SQL Server fall into this family.
| Type of SQLi | Representative technique | How to detect |
|---|---|---|
| In-band / UNION | UNION SELECT to append results | Extra data in the response; number of columns |
| In-band / Error-based | extractvalue(), invalid conversions | DBMS error messages leaking content |
| Blind / Boolean | AND 1=1 vs AND 1=2 | Page difference between T/F conditions |
| Blind / Time-based | SLEEP(5), pg_sleep(5) | Conditional latency in the response |
| Out-of-band | DNS/HTTP exfil (UTL_HTTP) | Network requests leaving the database |
NoSQL Injection: the problem doesn't disappear
NoSQL databases (MongoDB, CouchDB, Redis) are also injectable, but the vector shifts from SQL syntax to the manipulation of operators and document structure. CWE-943 covers exactly this scenario.
Operator injection in JSON
In APIs that receive JSON and pass it straight to the MongoDB driver, the attacker replaces a scalar value with an object containing operators. A login expecting {"user":"ana","pass":"x"} can be bypassed with:
{ "user": "admin", "pass": { "$ne": null } }
The $ne (not equal) operator makes the password condition match any non-null value, authenticating without knowing the password. Variations use $gt, $regex or $where to extract data or build blind oracles analogous to boolean/time-based SQLi.
Injection in query strings and in $where
Frameworks that accept nested URL parameters (for example user[$ne]=null via querystring) automatically translate these pairs into objects with operators, reproducing the attack above without the attacker needing to send JSON. This is a common vector in Express APIs using qs and urlencoded bodies.
Server-side JavaScript
Features like $where and mapReduce evaluate JavaScript on the server. If the expression is built with user input, it opens arbitrary code execution within the database context, with payloads like '; return true; var x=' capable of turning a filter condition into a total bypass or a blind oracle. MongoDB's recommendation is to disable javascriptEnabled and avoid $where whenever possible, and never build these expressions with input concatenation.
Definitive prevention
The good news: injection is one of the most eliminable classes in all of application security. The defenses below follow the OWASP SQL Injection Prevention Cheat Sheet, in order of priority.
1. Prepared statements / parameterized queries (defense number 1)
The only defense that eliminates the flaw at its root is separating code from data using parameterized queries. The query text goes precompiled; the values travel through parameters that are never interpreted as syntax.
// SECURE (Java / JDBC)
PreparedStatement ps = conn.prepareStatement(
"SELECT * FROM users WHERE email = ? AND password = ?");
ps.setString(1, email);
ps.setString(2, password);
# SECURE (Python / psycopg2)
cur.execute(
"SELECT * FROM users WHERE email = %s AND password = %s",
(email, password))
In NoSQL, the equivalent is to use the driver's methods with strict types instead of passing raw objects from the client, and to validate that comparison fields are scalars. Note that the defense is not escaping quotes, but never allowing the input to participate in the grammar of the query. That's why this technique is considered the only one that eliminates the entire class, rather than merely mitigating known payloads.
An important detail: parameters only cover values. Table names, column names and sort direction (ASC/DESC) can't be parameterized in most drivers. When these elements are dynamic, they must be resolved by an explicit allowlist in the code (a map of permitted values), never interpolated directly.
2. Stored procedures
Parameterized procedures offer equivalent protection provided that they don't build dynamic SQL internally via concatenation. A procedure that does EXEC('SELECT ... ' + @param) reintroduces the vulnerability.
3. ORMs, with care
ORMs (Hibernate, Sequelize, Django ORM, Prisma) use parameterization under the hood and are safe on the happy path. The risk lives in the gaps: raw queries (session.createNativeQuery, .raw(), $queryRawUnsafe) and dynamic column/sort names. Anything raw demands the same parameter discipline.
4. Input validation by allowlist
Validate the format, type and domain of each field against an allowlist of permitted values, never a denylist. For NoSQL, this means rejecting payloads whose expected scalar value comes as an object or contains keys starting with $. Do explicit type casting (string to string, number to number) before passing to the driver.
5. Least privilege on the database
The account used by the application should have only the necessary privileges. No DROP, no FILE, no access to system tables. That way, even if an injection escapes, the blast radius stays contained. Separate read and write accounts by context.
6. Escaping as a last resort
Escaping special characters is fragile, dependent on the DBMS and the charset, and should only be used when parameterization is genuinely impossible (for example, dynamic identifiers previously validated against an allowlist). Never treat escaping as a primary defense.
Defense in depth
Secure code is the core, but additional layers reduce residual risk and provide detection:
- WAF: rules (such as the OWASP CRS in ModSecurity) block known payloads at the edge. Useful against automated attacks, but no substitute for fixing the code.
- RASP: runtime instrumentation that observes the query actually sent to the database and blocks structural deviations.
- Monitoring: alert on bursts of SQL errors, latency spikes (time-based) and unexpected outbound connections from the database (out-of-band).
Testing: find it before the attacker
Injection handling must be verifiable:
- SAST: static analysis of the source code identifies concatenation in queries and unsanitized data flows (taint analysis).
- DAST: dynamic testing against the running application, with automated boolean/time-based payloads.
- sqlmap: the reference tool for exploiting and confirming SQLi in authorized pentesting. Use only with scope and written authorization; use without authorization is a crime.
Why relying on a single layer is dangerous
Mature teams combine all the defenses because each fails in a different way. A WAF is circumventable through creative encoding and new payload variants; a poorly maintained allowlist accumulates exceptions; least privilege doesn't prevent reading the data the application itself needs to access. Only parameterization in the code shuts the front door, but the outer layers buy detection time and reduce impact when something escapes. That is the essence of defense in depth applied to A03:2021.
It's also worth instituting a review standard: no query built by concatenation should pass code review, and every raw ORM call needs justification and explicit review. In large projects, lint rules and SAST gates in the pipeline make this policy automatic, preventing the flaw from reappearing with each new feature.
How Decripte helps
Decripte is a B2B cybersecurity company serving organizations from 1 to over 100,000 employees. Our Application Security and pentest practices identify SQLi and NoSQLi in code and at runtime, prioritize by real business risk and track remediation through to definitive parameterization, with retesting and integration into your development cycle.
Want to start mapping your exposure at no cost? Start free through our Intelligence Center, or explore the AppSec and pentest plans sized for your operation.
References
- OWASP Top 10 - A03:2021 Injection
- OWASP SQL Injection Prevention Cheat Sheet
- OWASP Testing Guide - Testing for SQL and NoSQL Injection
- MITRE CWE-89 - Improper Neutralization of Special Elements used in an SQL Command
- MITRE CWE-943 - Improper Neutralization of Special Elements in Data Query Logic
