As part of the OpenID protocol a relying party often establishes shared secrets (called ‘associations’) with identity providers that are used to verify identity assertions. It occurred to me that an OpenID relying party might easily introduce a major security hole in the process of establishing an association that could allow identity spoofing.
Each association is assigned a handle, which is a name by which the relying party and the provider will refer to the shared secret in later transactions. The potential security hole is possible because the Provider alone determines the association handle. If the relying party is not careful in saving associations it creates, a rogue Provider could hijack another Provider’s association with the relying party and thereby gain the ability to assert the identity of any user from the other Provider. Here’s a scenario:
- Victim hosts his identity with GoodOP, and has logged into a vulnerable RP and saved some private data.
- Hacker hosts EvilOP, which is a carefully contrived Provider rigged to hack into RPs.
- Hacker attempts to log into RP as any account hosted by GoodOP and can thereby discover the handle CompromisedHandle of the shared association between RP and GoodOP.
- Hacker instructs EvilOP to assign CompromisedHandle as the handle for the next association it creates with an RP.
- Hacker starts a login at RP with a Claimed Identifier that points at EvilOP. The RP then establishes an association with EvilOP as a preliminary step to the login process.
- EvilOP tells the RP of the new association and says the handle for it is CompromisedHandle.
- RP is vulnerable and overwrites the shared secret it has with GoodOP with the new one it established with EvilOP. Yet CompromisedHandle is still associated with GoodOP in the RP’s associations table.
- Denial of Service: The RP can no longer log in users from GoodOP, because the shared secret between them is wrong and the RP will reject identity assertions from GoodOP due to invalid signatures.
- Identity Spoofing: EvilOP now can write identity assertions on behalf of GoodOP such that RP thinks they are from GoodOP. Hacker can use EvilOP to write assertions and log in as anyone who has an account with GoodOP.
The good news is that having come up with this possible security hole, I did a check of DotNetOpenId and Janrain’s OpenID Ruby library. Neither one was vulnerable to this. Since all of Janrain’s libraries are similar to each other, I ended my investigation because it was likely that all the other Janrain libraries were also secure in this regard.
Still, this is another argument for web sites to use standard libraries for their OpenID support rather than trying to implement OpenID themselves. There are just too many potential security holes for a webmaster to avoid them all unless authentication is truly his focus and passion.
Step 7 is not possible? I'd guess the RP stores associations per OP, so an OP cannot overwrite any other OP's associations
I don't know how step 3 would be possible. The Web browser will never be able to read the handle established between RP and GoodOP, isn't the association created in a HTTP connection directly between the RP and GoodOP.
Even if the Hacker could read the association between RP and GoodOP, this association wouldn't be the same as the one between the victim and GoodOP, because GoodOP probably derive the handle from a signed hash of the username & random number & timestamp, or something like that? So the handle that EvilOP gets, would be different from the one between RP and GoodOP.
Step 7 mentions that the RP must be vulnerable to this attack. As you say, the RP shouldn't be vulnerable, but it *may* be.
Step 3 is always possible, as the browser (and thus the attacker) can see the association handle after the RP and OP establish it by their privat echannel, because during the checkid_setup redirect one of the user-visible parameters is the association handle.
Shared association handles are not per-user at all, as the point of shared associations is to reuse across many logins between an RP and OP. The secret of the association need not be the same between the good and evil OPs (in fact the point is they are not) but the assoc_handle is contrived to be the same, as evilOP can be configured to target a known assoc_handle.
Andrew, I realize this is several years later, so you may not remember. What exactly *is* the preferred solution here? Flipping through Janrain's code, I'm hard pressed to pin down where they solve the problem. How can the return_to script be sure that the assoc_handle it receives is not hijacked – when it passes the very same assoc_handle in outbound requests, which are easily viewed by anyone with a local proxy?
The solution is simply that the RP stores the associations by both the association handle *and* OP endpoint that the association is with. That way, any association handle "collisions" between OP endpoints don't occur.
Protecting the return_to script or the fact that the assoc_handle is visible to eavesdroppers is irrelevant to the fix.
I'm still missing some subtlety. The return_to script will receive an HTTP request from some source, containing an association handle that may or may not be hijacked. The source of the HTTP request may say that it is a particular GoodOP returning the result of a authorization request – in my case Google, using federated login – but I don't think I can rely on the HTTP request to establish the identity of the OP, because that can be faked as well.
In the OpenID docs, I see that they require that the openid_sig be validated, as well as the openid_response_nonce. Since that code is annoying, I'd rather not write it, and just use something like Janrain instead. But your solution sounds simpler, and like something I'd be willing to deal with myself. Please illuminate further?
When you say "return_to script" are you referring to Javascript? OpenID isn't a security protocol for javascript clients — it's for web servers. This blog post and my earlier comment is only around securing against one possible exploit of the protocol. response nonces, signatures, etc. all play very important parts in other security concerns of the protocol. And I agree, you should *not* implement it yourself. Janrain is a good option. If your web site is based on .NET then DotNetOpenAuth is another option that I personally invest a lot of time in developing. With DotNetOpenAuth (and I believe Janrain as well) you should be well protected from this and many other security vulnerabilities with little or no additional effort on your part.
I'm referring to a PHP script, not Javascript. The PHP (or ASP.NET, or whatever) script doesn't have any way to know that the sender of the HTTP request purporting to be the last stage of an authorization actually emerged from a legit OP.
I think I went off in a different direction than you did, though. I see that your sequence describes a scenario where the Evil OP is a legitimate, but evil, provider, that is sending back a hijacked association handle and then creating a new association handle, overwriting the Good OP's handle. I was imagining a scenario in which the bad guy hijacks the original association handle and then simply sends back an id_res HTTP request purporting to be the Good OP. But, on closer examination and with your comments, I see that the nonce and signature verification should account for that scenario.
It's largely academic for me at this point, since my own efforts have led more to understanding the technology than to a workable solution; I'll replace the guts of my solution with Janrain's PHP library. Thanks for sharing your thoughts.