The Consumer Key That Opened Enterprise Mail — Java Bug Hunt
Modelled on Storm-0558 (disclosed July 2023): a China-based threat actor forged authentication tokens with an acquired Microsoft account (MSA) consumer…
- Language: Java
- Layer: Backend
- Difficulty: Medium
- Concepts: Security, Auth
- Modelled on: Microsoft · Storm-0558 (2023)
- Visible tests: a genuine enterprise token validates; a consumer key cannot sign an enterprise token
- Reward: 50 XP for a complete fix
Briefing
Modelled on Storm-0558 (disclosed July 2023): a China-based threat actor forged authentication tokens with an acquired Microsoft account (MSA) consumer signing key and used them to read enterprise email at a number of organisations, including US government agencies. Microsoft's investigation found that a token-validation flaw allowed the consumer key to be trusted for enterprise tokens.
This project is a reconstruction. TokenValidator checks the token's issuer and signature, but it looks the signing key up in the whole key ring — so a key that belongs to a different issuer is happily accepted as long as the signature verifies.
Fix validate so a token is only verified with a key that its own issuer publishes.
Bug report
BUG-S0558 · Priority: Critical (token forgery) · Reported by: incident response
TokenValidator.validate(ring, token, expectedIssuer) returns, checking in order:
- "wrong-issuer" if token.iss != expectedIssuer
- "untrusted-key" if token.kid is not one of ring.keysFor(token.iss) — a key held in the ring for ANOTHER issuer does not count
- "bad-signature" if Signer.sign(key.secret, token.payload()) != token.sig
- "ok" otherwise
Observed: a token signed with the consumer key "msa-1" but claiming the enterprise issuer validates as "ok" at the enterprise endpoint.
Logs
[owa] token kid=msa-1 iss=https://sts.windows.net/contoso sub=ceo@contoso -> ok
[audit] mailbox access by forged enterprise token signed with consumer keyThe code as shipped
src/auth/TokenValidator.java (editable)
class TokenValidator {
// Returns "ok" or the reason the token is refused.
static String validate(KeyRing ring, Token t, String expectedIssuer) {
if (!t.iss.equals(expectedIssuer)) return "wrong-issuer";
SigningKey key = ring.find(t.kid);
if (key == null) return "untrusted-key";
if (!Signer.sign(key.secret, t.payload()).equals(t.sig)) return "bad-signature";
return "ok";
}
}
Read-only context: src/auth/KeyRing.java, src/auth/Token.java.
Open the hunt to edit the files, run the visible tests and submit against the hidden ones. More Java bug hunts.