Swagger and OpenAPI Exposure: Turning Specs Into Bug Bounty Finds
Finding /swagger-ui/ is not a vulnerability by itself. Finding a live OpenAPI document that names internal hosts, admin operations, and forgotten debug routes is recon gold. The report only sticks when you convert that map into a bounded authorization or injection proof.
I treat every published spec as a guided tour the developers left for me.
Where the documents hide
Probe more than the textbook paths. Useful hits include:
/swagger.json,/openapi.json,/v3/api-docs/swagger-ui/index.html,/docs,/redoc- Staging hosts, versioned gateways, and old
api-subdomains - Mobile or partner hosts that still ship a full schema
Download the JSON or YAML. Search for internal, admin, debug, bypass, x- vendor extensions, and servers pointing at private hostnames. A servers entry with an IP from a non-public range, or a path tagged deprecated but still live, is often more interesting than the pretty UI.
Try the documented base URL with and without authentication. Some UIs load publicly while the underlying /v3/api-docs needs a session—or the reverse. Older Spring stacks sometimes leave both /swagger/v1/swagger.json and a versioned twin online; diff them for operations that vanished from the UI but still answer on the wire.
Turn endpoints into tests
Group operations by risk: object IDs in paths, file upload, password reset, invitation accept, webhook registration, and anything that accepts a URL. For each high-value route, note required headers, security schemes, and sample schemas.
Then leave Swagger alone and hit the real API with two accounts you own. Swap resource IDs across tenants. Call an admin-tagged operation with a low-privilege token. Send the documented optional fields the UI never shows. Specs lie about enforcement more often than they lie about existence.
Watch for security: [] on individual operations. A global bearer requirement with one anonymous exception is where invite accept, password reset, or half-admin helpers often live. Hit those with no cookie first, then with a member session if anonymous fails.
A clean proof looks like: "OpenAPI lists DELETE /admin/users/{id}; member token receives 200 and removes a canary user in my tenant." That is broken function-level authorization with a documentation assist—not "Swagger is exposed."
What not to file
Public marketing APIs sometimes publish intentional developer portals. If every sensitive operation returns 401 for anonymous callers and object checks hold across accounts, the exposure may be informational. Still note secrets that should never appear in schemas: sample API keys, real customer IDs, internal hostnames, or credentials in example values.
Do not scrape the entire catalog into a wall of screenshots. Pick three routes, prove one meaningful failure, and attach the relevant schema fragment. Triage reads faster when the attachment is one operation object, not the whole multi-megabyte document.
Severity and fixes
Impact rides on what the spec unlocks. A documented IDOR on billing records beats a public health-check listing. Mention whether the UI was reachable without VPN, whether examples leaked secrets, and which role boundaries failed.
Recommend splitting public and private specs, disabling Swagger in production when unused, gating docs behind the same SSO as admin tools, stripping internal servers from published documents, and generating schemas without live secrets. The durable win is authorizing every documented operation the same way the UI claims.
When triage asks "is docs exposure enough?", answer with the request that actually crossed a trust boundary. The OpenAPI file is how you found the door. The auth bug is why it pays.