Shadow API Discovery and Security Testing
Shadow APIs are the attack surface that organizations don't know they have. They're the /v1/ endpoints that weren't decommissioned when /v2/ launched. The internal admin routes that weren't supposed to be internet-facing. The debug endpoints left behind after a deployment. The endpoints mobile apps call that never made it into the API documentation.
They're dangerous precisely because they fall outside the security controls applied to documented APIs: no rate limiting, weaker authentication, no WAF rules, stale access control logic. OWASP API Security Top 10 specifically calls out Improper Inventory Management (API9:2023) — failing to know your own API surface is a vulnerability class on its own.
This guide covers how to find shadow APIs and what to test once you find them.
Where Shadow APIs Come From
- API versioning without retirement: v1 is left running after v2 ships. v1 has weaker security controls than v2.
- Internal services accidentally exposed: A microservice meant for internal traffic gets routed through the load balancer.
- Developer and debug endpoints:
/debug,/actuator,/metrics,/trace,/envleft in production builds. - Third-party integrations: Webhook receivers, callback URLs, and partner APIs that bypass the main API gateway.
- Mobile app endpoints: Dedicated endpoints for iOS/Android apps that aren't in the web API docs.
- Legacy SOAP/XML endpoints: Old SOAP services left running alongside new REST APIs.
- Admin interfaces:
/admin,/management,/consoleroutes exposed unintentionally.
Discovery Technique 1: JavaScript Endpoint Extraction
Modern SPAs load their entire API client in JavaScript. Every API endpoint the frontend calls is a string somewhere in that JS bundle. Extract them:
# Download all JS from the target
# First, spider the site and collect all .js URLs
# In Burp: Target → Site map → right-click → Extract JS links
# Then extract API paths from each JS file
grep -oE '"(/api/[^"]+)"' *.js | sort -u
grep -oE "'(/api/[^']+)'" *.js | sort -u
grep -oE '`(/api/[^`]+)`' *.js | sort -u
# More comprehensive extraction
grep -oE '(GET|POST|PUT|DELETE|PATCH) ["/\047][a-zA-Z0-9/_-]+' *.js | sort -u
Automated tools for this:
# LinkFinder: extracts endpoints from JS files
python3 linkfinder.py -i https://target.com -d -o cli
# SecretFinder: finds endpoints AND secrets in JS
python3 SecretFinder.py -i https://target.com/app.bundle.js -o results.html
# gau (GetAllURLs): historical endpoint discovery
gau target.com | grep "api\|endpoint\|v[0-9]" | sort -u
Discovery Technique 2: API Versioning Enumeration
If you find /api/v2/users, systematically test earlier versions:
# Version enumeration with ffuf
ffuf -u https://target.com/api/FUZZ/users -w versions.txt
# versions.txt:
v1
v2
v3
v0
v1.0
v1.1
v2.0
v3.0
1
2
3
beta
alpha
staging
internal
dev
test
old
legacy
Also test version in headers (some APIs use header versioning):
curl -H "API-Version: 1" https://target.com/api/users
curl -H "Accept: application/vnd.api.v1+json" https://target.com/api/users
curl -H "X-API-Version: 1.0" https://target.com/api/users
Discovery Technique 3: Path and Parameter Fuzzing
# Fuzz for hidden endpoints under a known path
ffuf -u https://target.com/api/v2/FUZZ \
-w /usr/share/seclists/Discovery/Web-Content/api/api-endpoints.txt \
-H "Authorization: Bearer $TOKEN" \
-mc 200,201,301,302,400,403 \
-fs 0
# Fuzz for parameters on existing endpoints
ffuf -u "https://target.com/api/v2/users?FUZZ=test" \
-w /usr/share/seclists/Discovery/Web-Content/api/api-params.txt \
-H "Authorization: Bearer $TOKEN" \
-mc 200,400 -fr "invalid parameter"
For GraphQL, introspection reveals the full schema even when it's not documented:
curl -X POST https://target.com/graphql \
-H "Content-Type: application/json" \
-d '{"query":"{__schema{types{name fields{name}}}}"}'
# If introspection is disabled, try field suggestion enumeration
# GraphQL often returns "Did you mean X?" errors that reveal field names
Discovery Technique 4: Historical Records
# Wayback Machine (web.archive.org)
gau target.com | grep "api\|/v1\|/v2\|admin\|internal" | sort -u
# Certificate transparency logs reveal subdomains
crt.sh: https://crt.sh/?q=%.target.com&output=json
# Google dorking
site:target.com/api
site:target.com inurl:/v1/
site:target.com inurl:/internal/
site:target.com inurl:/admin/
# GitHub code search for exposed config/API URLs
org:target-company "api.target.com" OR "/api/v1" OR "internal.target.com"
Discovery Technique 5: Mobile App Decompilation
Mobile apps often call a separate API surface. Decompile them to extract all API endpoints:
# Android APK
# Download APK from APKPure or device backup
apktool d target.apk -o decompiled/
# Search for API endpoints
grep -r "https://api\|/api/\|/v[0-9]/" decompiled/ --include="*.smali"
grep -r "api\." decompiled/ --include="*.xml"
# For React Native apps (JavaScript bundle)
find decompiled/ -name "*.bundle" -o -name "*.jsbundle"
strings index.android.bundle | grep -E "https?://[^ ]+"
# iOS IPA
unzip target.ipa
strings Payload/target.app/target | grep -E "https?://[^ ]+"
# jadx for Java decompilation (better readability)
jadx -d output/ target.apk
grep -r "/api/" output/ --include="*.java" | sort -u
Discovery Technique 6: Framework Default Endpoints
Specific frameworks expose well-known management endpoints. Test for all of them:
| Framework | Common Shadow Endpoints |
|---|---|
| Spring Boot (Java) | /actuator, /actuator/env, /actuator/heapdump, /actuator/beans |
| Django | /admin, /__debug__, /api-auth |
| Laravel | /telescope, /horizon, /_debugbar |
| Express.js | /api-docs, /swagger, /status, /health |
| Rails | /rails/info, /rails/mailers |
| GraphQL | /graphql, /graphiql, /graphql-playground |
| gRPC-gateway | /grpc-gateway, gRPC reflection endpoint |
| Any REST API | /swagger.json, /openapi.json, /api/docs, /api/spec |
Testing Shadow APIs Once Found
1. Authentication Bypass
Shadow APIs often have weaker authentication than the main API. Test with:
- No authentication at all — remove the Authorization header
- Expired tokens — use an old token that would fail on the main API
- Different user's tokens — does v1 enforce ownership checks that v2 doesn't?
- Empty credentials —
Authorization: Bearerwith no token - Invalid format —
Authorization: Basic dXNlcjpwYXNzon a Bearer-only API
2. Broken Object Level Authorization (BOLA/IDOR)
Old API versions frequently lack the access control improvements added in newer versions:
# Test v1 IDOR — can user A access user B's data?
# Normal request (v2 - properly secured)
GET /api/v2/users/12345/profile
Authorization: Bearer UserA_Token
# Shadow API request (v1 - may lack access control)
GET /api/v1/users/99999/profile
Authorization: Bearer UserA_Token
# Should return 403 — if it returns 200, it's BOLA
3. Mass Assignment
Old endpoints may not have property allowlisting. Send extra fields and see if they're applied:
PUT /api/v1/users/me
{
"name": "Test User",
"role": "admin", // Should be ignored, may not be in v1
"isAdmin": true, // Same
"credits": 99999
}
4. Rate Limiting and Brute Force
# Rate limiting often only applied to the current API version
# Test login brute force against old endpoints
for i in $(seq 1 100); do
curl -X POST https://target.com/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"user@test.com","password":"attempt'$i'"}'
sleep 0.1
done
# Check if you get locked out (good) or never (bad)
5. Spring Boot Actuator Exploitation
If Spring Boot Actuator endpoints are exposed, they can lead to full application compromise:
# Dump environment variables (may contain DB passwords, API keys)
curl https://target.com/actuator/env | jq '.'
# Heap dump (may contain JVM memory with cleartext secrets)
curl https://target.com/actuator/heapdump -o heapdump.hprof
# Analyze with Eclipse Memory Analyzer (MAT) for secrets
# Remote code execution via /actuator/restart + logfile injection
# Covered in detail in Spring Boot security testing guides
Building a Shadow API Inventory
Document everything you find. For each shadow endpoint:
| Field | Record |
|---|---|
| Endpoint URL | Full path with version prefix |
| Discovery method | JS extraction / version brute-force / historical / mobile |
| Authentication required? | Yes / No / Weaker than main API |
| Rate limited? | Yes / No |
| Data returned | PII / credentials / internal state |
| Actions available | Read-only / Write / Admin operations |
| Severity | Critical (admin access) to Info (200 response) |
Remediation
Maintain an API inventory: Every API endpoint in production should be registered. No endpoint should go live without it appearing in the inventory. Tooling like Akamai API Security, Noname, or Salt Security does this automatically from traffic.
Enforce retirement policies: When a new API version ships, the old one gets a sunset date. After the date, it returns 410 Gone. Not optional.
Block debug endpoints at the infrastructure level: Actuator, debug, and admin routes should be blocked at the load balancer or API gateway before they reach the application — even if the application exposes them.
Apply the same security controls to all API versions: Authentication, authorization, rate limiting, and WAF rules should apply to every version, not just the current one. A /v1/ endpoint with weaker auth is a vulnerability regardless of its age.
Run API discovery scans regularly: What you don't know about you can't protect. Automated external scanning that probes for shadow endpoints should run on every deployment, not just during annual penetration tests.
Ironimo discovers undocumented API endpoints using the same techniques pentesters use manually — JavaScript extraction, version enumeration, framework-specific probing, and parameter mining. Get a full inventory of your API attack surface, not just what's in your Swagger docs.
Start free scan