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

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:

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
← Back to Blog