This is an amazing bug, I am indeed surprised this happened in such a critical protocol. My guess is that nobody must have clearly specified the protocol, and anyone would have been able to catch that in an abstracted english spec.
If this is not the issue, then the implementation might be too complex for people to compare it with the spec (gap between the theory and the practice). I would be extremely interested in a post mortem from Apple.
I have a few follow up questions.
1. seeing how simple the first JWT request is, how can Apple actually authenticate the user at this point?
2. If Apple does not authenticate the user for the first request, how can they check that this bug wasn’t exploited?
The bug is not in the protocol. The bug is about the extra value addition that apple was doing by letting the user choose any other email address.
1. The account take over happens on the third party sites that use the apple login.
2. This seems like a product request to add value to user by providing a relay email address of a user's choice.
From the report- `I found I could request JWTs for any Email ID from Apple and when the signature of these tokens was verified using Apple’s public key, they showed as valid.`
It's not a bug with protocol or security algorithm. A lock by itself does not provides any security if its not put in the right place.
It's exploitable through apple's web-based login flow used by web sites and Android devices. There are multiple round trips between the user and apple, and state is passed over the wire. The state could be modified at a certain point in the flow to cause the final result (the JWT) to be compromised. The flow is still the same, they seem to have fixed it entirely by adding checks server-side.
All your questions can be answered by reading “Sign in with Apple REST API” [1][2]:
1. User clicks or touches the “Sign in with Apple” button
2. App or website redirects the user to Apple’s authentication service with some information in the URL including the application ID (aka. OAuth Client ID), Redirect URL, scopes (aka. permissions) and an optional state parameter
3. User types their username and password and if correct Apple redirects them back to the “Redirect URL” with an identity token, authorization code, and user identifier to your app
4. The identity token is a JSON Web Token (JWT) and contains the following claims:
• aud: Your client_id in your Apple Developer account.
• exp: The expiry time for the token. This value is typically set to five minutes.
• iat: The time the token was issued.
• nonce: A String value used to associate a client session and an ID token. This value is used to mitigate replay attacks and is present only if passed during the authorization request.
• nonce_supported: A Boolean value that indicates whether the transaction is on a nonce-supported platform. If you sent a nonce in the authorization request but do not see the nonce claim in the ID token, check this claim to determine how to proceed. If this claim returns true you should treat nonce as mandatory and fail the transaction; otherwise, you can proceed treating the nonce as optional.
• email: The user's email address.
• email_verified: A Boolean value that indicates whether the service has verified the email. The value of this claim is always true because the servers only return verified email addresses.
• c_hash: Required when using the Hybrid Flow. Code hash value is the base64url encoding of the left-most half of the hash of the octets of the ASCII representation of the code value, where the hash algorithm used is the hash algorithm used in the alg Header Parameter of the ID Token's JOSE Header. For instance, if the alg is HS512, hash the code value with SHA-512, then take the left-most 256 bits and base64url encode them. The c_hash value is a case sensitive string
Let's start with the fact that Apple is forcing people to use an E-mail address as a user ID. That's just straight-up stupid.
How many members of the public think that they have to use their E-mail account password as their password for Apple ID and every other amateur-hour site that enforces this dumb rule?
MILLIONS. I would bet a decent amount of money on it. So if any one of these sites is hacked and the user database is compromised, all of the user's Web log-ins that have this policy are wide open.
Then there's the simple fact that everyone's E-mail address is on thousands of spammers' lists. A simple brute-force attack using the top 100 passwords is also going to yield quite a trove, I'd imagine.
Apple IDs didn't originally have to be E-mail addresses. They're going backward.
The thing that made this bug possible was because, while your Apple ID has to be an email address, Apple has a mechanism to avoid exposing it to third parties - unlike Google, Apple, or Facebook's single sign-on implementation; the bug seems to be in the step between verifying your identity and telling Apple whether you would or would not like your email address to be exposed.
If anything, the issue is that third parties treat the email address as a unique, unchangeable identity, and then agree to rely on Apple's assertion of what your email address is. But given how hard identity is - and the challenges in dealing with passwords, account recovery, and name changes at scale - it's a pretty reasonable tradeoff to make.
If this is not the issue, then the implementation might be too complex for people to compare it with the spec (gap between the theory and the practice). I would be extremely interested in a post mortem from Apple.
I have a few follow up questions.
1. seeing how simple the first JWT request is, how can Apple actually authenticate the user at this point?
2. If Apple does not authenticate the user for the first request, how can they check that this bug wasn’t exploited?
3. Anybody can explain what this payload is?
{ "iss": "https://appleid.apple.com", "aud": "com.XXXX.weblogin", "exp": 158XXXXXXX, "iat": 158XXXXXXX, "sub": "XXXX.XXXXX.XXXX", "c_hash": "FJXwx9EHQqXXXXXXXX", "email": "contact@bhavukjain.com", // or "XXXXX@privaterelay.appleid.com" "email_verified": "true", "auth_time": 158XXXXXXX, "nonce_supported": true }
My guess is that c_hash is the hash of the whole payload and it is kept server side.