SQL injection in an API happens when attacker-controlled input reaches your database query without being safely separated from the SQL code, letting someone read, change, or delete data they should never touch. It shows up anywhere an API accepts input (JSON bodies, query strings, path parameters, headers) and passes that input into a database query as raw text. The core fix is simple and reliable: use parameterized queries (also called prepared statements) so user input is always treated as data, never as executable SQL.
Content Table
How SQL Injection Reaches an API
APIs feel different from old-school web forms, but the vulnerability is identical under the hood. Your endpoint receives input, builds a SQL string, and sends it to the database. If any part of that string is glued together with untrusted input, an attacker can break out of the intended query and inject their own commands.
Common entry points in a REST or GraphQL API include:
-
Query parameters
:
/users?sort=namewheresortgets dropped into anORDER BYclause. -
Path parameters
:
/products/42where42becomes part of aWHERE id =filter. -
JSON request bodies
: a
filterorsearchfield on a POST endpoint. -
Headers and cookies
: values like an
Authorizationtoken or tenant ID used in a lookup.
SQL injection is covered under the Injection category (A03:2021) of the OWASP Top 10. The OWASP API Security Top 10 (2023) has no separate SQL injection item. It remains one of the most damaging API security vulnerabilities precisely because it is so easy to introduce and so devastating when exploited.
What a Vulnerable API Endpoint Looks Like
Here is a typical unsafe pattern. A Node.js API takes a username from a JSON body and builds the query by string concatenation:
// VULNERABLE - do not use
app.post('/login', (req, res) => {
const { username, password } = req.body;
const query =
"SELECT * FROM users WHERE username = '" + username +
"' AND password = '" + password + "'";
db.query(query, (err, rows) => { /* ... */ });
});
If an attacker sends
{"username": "admin'--", "password": "x"}
, the query becomes:
SELECT * FROM users WHERE username = 'admin'--' AND password = 'x'
The
--
comments out the password check, and the attacker logs in as admin. Worse payloads like
' OR 1=1--
return every row, and
'; DROP TABLE users;--
can destroy data on databases that allow stacked queries.
How to Detect SQL Injection in APIs
You cannot fix what you cannot see. Combine automated scanning with manual review and runtime monitoring.
| Method | What it catches |
|---|---|
| Static analysis (SAST) | String-concatenated queries in source code before deploy |
| Dynamic scanning (DAST) | Live endpoints that respond to injected payloads |
| Code review | Subtle cases where ORMs are bypassed with raw SQL |
| Log monitoring | Spikes in SQL errors, unusual query patterns, repeated 500s |
Manual test signals to watch for when probing an endpoint:
-
A single quote (
') in a parameter triggers a database error or a 500 response. -
Boolean payloads change results:
id=42 AND 1=1returns data,id=42 AND 1=2returns nothing. -
Time-based payloads like
' OR SLEEP(5)--cause a measurable delay, revealing blind injection. - Error messages leak table names, column names, or SQL syntax back to the client.
How to Prevent SQL Injection
The fix is not a filter that tries to block "bad" characters. Blacklists always miss something. The reliable fix is to stop mixing code and data.
1. Use parameterized queries (prepared statements)
Send the SQL and the values separately. The database driver binds the values as data, so quotes and semicolons carry no special meaning.
// SAFE - parameterized query
const query = "SELECT * FROM users WHERE username = ? AND password = ?";
db.query(query, [username, password], (err, rows) => { /* ... */ });
Every major language supports this natively:
?
or
$1
placeholders in Node.js and PostgreSQL,
%s
with a params tuple in Python's psycopg2, and
PreparedStatement
in Java. Prepared statements are your primary defense, and in most code they are the only one you need for query building. Stored procedures are not automatically safe either; dynamic SQL built by concatenation inside them is injectable.
2. Prefer an ORM or query builder
Tools like Sequelize, Prisma, SQLAlchemy, and Hibernate generate parameterized SQL for you. They are not magic (raw query escape hatches reintroduce risk), but for standard CRUD they eliminate most hand-written string building.
3. Validate and constrain input
Parameterization protects values, but some parts of a query cannot be parameterized, like column names in
ORDER BY
or a table name. Escaping is a last resort, not a primary defense. For those identifiers, use an allowlist instead of passing input through:
const allowed = ['name', 'created_at', 'price'];
const sortColumn = allowed.includes(req.query.sort) ? req.query.sort : 'name';
Enforcing strict input shapes at the edge stops malformed data early. Defining a contract with JSON Schema validation rejects payloads whose types or formats do not match before they ever reach a query.
4. Apply least privilege to the database account
The account your API uses should only have the permissions it needs. A read-only endpoint should connect with a read-only user. That way, even a successful injection cannot drop tables or write data.
Defense Layers Beyond Queries
Parameterized queries stop the injection itself. These layers reduce blast radius and slow down attackers who are probing your endpoints:
- Rate limiting: automated injection tools fire thousands of payloads. Throttling requests makes fuzzing painfully slow. See how services control traffic with API rate limiting.
-
Generic error responses: never return raw SQL errors to the client. Log the detail server-side and send a plain
400or500. - Web Application Firewall (WAF): a WAF can block obvious injection signatures as a backstop, though it should never be your only defense.
- Consistent contracts across versions: when you change endpoints, keep validation in sync. Planning changes with clear API versioning strategies prevents old, unvalidated routes from lingering after an upgrade.
Layered together, these practices turn SQL injection from an easy win into a dead end. The single most important habit remains constant: separate SQL code from user data with prepared statements everywhere a query touches input.
Read minified API handler code
Minified or tangled handler code hides string-concatenated SQL. Our JS beautifier makes it readable so unsafe query building is easy to spot during review.
Try the JS beautifier →
No. JSON is just a transport format. If your API reads a JSON field and concatenates it into a SQL query, it is exactly as vulnerable as an old HTML form. The payload lives inside the JSON value, so the injection risk is identical to any other input source.
Mostly, for standard operations, because they generate parameterized queries automatically. But ORMs offer raw query methods and string interpolation escape hatches. The moment you build raw SQL by concatenating input inside an ORM, the protection is gone, so those spots still need parameterization.
The terms are used almost interchangeably. A prepared statement is a query the database compiles once with placeholders, then executes with bound values. Parameterized query describes the same idea of separating SQL from data. Both keep user input as data, never as executable code.
Not reliably. Blacklisting or escaping characters is fragile because attackers find encoding tricks and edge cases the filter missed. Sanitization and validation are useful extra layers, but parameterized queries are the actual fix. Use validation for allowlisting things you cannot parameterize, like column names.
Run tools like sqlmap or OWASP ZAP against a staging copy of your API that you own. Send probe payloads such as a single quote, boolean tests, and time-based delays, then watch for errors, changed results, or slow responses. Never test systems you do not have permission to assess.