Summary
- Class
- CWE-287 — Improper Authentication
- CVSS
- 9.9 (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:L)
- Endpoint
/backend/api/me- Vector
Authorizationheader — unsigned base64url token- Impact
- Full account takeover + privilege escalation to superuser
- Status
- Fixed and confirmed
Discovery
While testing a government web application's authenticated API, I intercepted the Authorization header being sent to /backend/api/me. The token was not a JWT — there was no signature segment and no alg header. Decoding it from base64url revealed a plain JSON object:
{
"email": "[REDACTED]@[REDACTED].fr",
"id": 1337,
"secret": "a1b2c3d4-e5f6-..."
}
The server-side logic worked as follows: it looked up the secret field against the user record matching id — this check passed because the secret was correct for my account. It then resolved the caller's identity from the email field independently, without verifying that the email belonged to the same user as the id/secret pair.
This meant the email field was an unverified, user-controllable identity claim inside an unsigned payload.
Exploitation
- Self-register on the platform and obtain a valid API token via
/backend/api/me - Base64url-decode the token to get the raw JSON
- Replace the
emailfield with any target user's email — including superuser/admin accounts - Re-encode the modified JSON to base64url
- Send the forged token in the
Authorizationheader
GET /backend/api/me HTTP/2
Host: [REDACTED].gouv.fr
Authorization: Bearer eyJlbWFpbCI6InZ...[forged base64url]...
The server validated my secret against my id (which passed), then resolved the session as the victim email — returning their full profile, session, and role.
By setting the email to a superuser account, I gained access to the admin journal and the personal data of all 1,288 registered users on the platform.
Impact
- Account takeover — any user's account can be impersonated by any registered user
- Privilege escalation — escalation to superuser role, including admin panel access
- Mass PII breach — personal data of 1,288 users accessible via the superuser journal
- GDPR implications — the application is a French government platform processing citizen data
Remediation
The development team pushed a fix to the application's public repository:
- Replaced unsigned base64 tokens with properly signed tokens (HMAC/JWT) — tampering the payload now invalidates the signature
- Changed identity resolution to derive the user from
token.user_idrather than a separate email field - Ensured each user's secret is a unique CSPRNG value bound to their account
- Added proper
401 Unauthorizedresponses for invalid or tampered tokens
The fix was confirmed through retesting.
Takeaways
If a token carries identity claims, those claims must be integrity-protected. A secret that proves "I own account X" should never be separable from the identity resolution of "I am user Y." Always check: can I decode this token? If yes — is there a signature? If not — can I modify a field and have the server accept it? This is the exact gap that signed tokens (JWT, HMAC) are designed to close.
