The OpenID 2.0 Compliance Crusade - Part I
Heine Mon, 2010/01/11 - 22:50
We released Drupal 6.14 because of a number of vulnerabilities in the OpenID core module. One of those vulnerabilities was caused by not obeying the OpenID 2.0 Authentication specification.
A number of other spec violations was discovered while working on the security issue. This might not be that be surprising, after all, our OpenID implementation was written against a draft, not the final 2.0 specification.
In addition, the issue queue on the OpenID core module hints that the OpenID module is going the way of BlogAPI (another Drupal dodo).
Rather than trying to fix each violation, I decided to correct the immediate issue and then start a belated OpenID 2.0 Compliance Crusade in public, to get our OpenID implementation fully compliant.
Wanna join in? Great! The rest of this post is meant to provide a slightly easier introdcution into the first part of OpenID than the official specs. To prevent disappointment: It's basically a partial retelling of the spec. With this introduction, you should be able to investigate spec violations, and file and review patches for OpenID.

A typical OpenID transaction has three parties involved: the user, the OpenID provider (OP) and the webapp that request authentication, called the relying party (RP).
Here, the RP is Drupal, and the OP, or OpenID Provider is Gmail, Yahoo, Live or MyOpenID.
Step 1. Initiation
The user kicks off the process by providing an identifier to Drupal. This user-supplied identifier can either be his own identifier (heine.myopenid.com) or identify the OpenID provider that should be used (yahoo.com). A user-supplied identifier that identifies the OpenID provider is called an "OP Identifier" in the spec. We can distinguish between the different identifiers after having performed the Discovery step.
Step 2. Discovery
Contacting the OpenID Provider
After normalizing the identifier (#575804, #578464), Drupal contacts the OpenID provider to inquire about its services. Drupal should follow each and all redirects (still part of the normalization) until it discovers the required information. Drupal should note the final URL as being the Claimed Identifier and use this URL during the authentication step. [huh? How does that jive with 7.3.1: "If the end user entered an OP Identifier, there is no Claimed Identifier."?]
Rules for discovery:
- If the identifier is an XRI, Drupal should do XRI resolution that will deliver an XRDS document.
- If the identifier is a URL, Drupal uses the YADIS protocol to fetch an XRDS document.
- Should Yadis fail, result in an invalid XRDS document, or result in an XRDS document without a SERVICE ELEMENT, Drupal should do HTML-based discovery
Interpreting the response
It's a bit hard to describe each and every discovery method, so I'll just go into detail on XRDS based discovery via XRI or Yadis (section 7.3.2), as this is the method used by Google (vulnerability was discovered using Google OpenIDs).
So, we have an XRDS document, basically an XML document with entries for services related to the Identifier. It looks something like:
<Type>http://specs.openid.net/auth/2.0/signon</Type>
<URI>https://www.exampleprovider.com/endpoint/</URI>
<LocalID>https://exampleuser.exampleprovider.com/</LocalID>
</service>
Drupal knows whether the user-supplied identifier is describing the user or the OpenID provider by following a series of steps.
First, Drupal should search throught the XRDS document, trying to find whether it contains an OP Identifier element which is a Service element, containing a Type tag with the text content "http://specs.openid.net/auth/2.0/server" and a URI tag (the text content is the OP endpoint URL Drupal must use to do authentication requests).
If Drupal cannot find this OP Identifier element, it should try to find a Claimed Identifier Element which is a Service element, containing a Type tag with the text content "http://specs.openid.net/auth/2.0/signon", a URI tag (containing the OP endpoint URL as above) and an optional LocalID tag (an identifier used by the OpenID provider to identify the user).
As you can see, the example code above doesn't contain an OP Identifier Element, but a Claimed Identifier Element. The response by Google, below contains an OP Identifier element; the Identifier describes the OpenID provider, not the user. The OpenID provider will allow the user to choose an identity when authenticating (ie which gmail account to login with).
<Type>http://specs.openid.net/auth/2.0/server</Type>
<Type>http://openid.net/srv/ax/1.0</Type>
<Type>http://specs.openid.net/extensions/ui/1.0/mode/popup</Type>
<Type>http://specs.openid.net/extensions/ui/1.0/icon</Type>
<Type>http://specs.openid.net/extensions/pape/1.0</Type>
<URI>https://www.google.com/accounts/o8/ud</URI>
</service>
As I wrote above Drupal should search for an OP Identifier Element, and only then for a Claimed Identifer Element. Right now, Drupal just looks at the first services entry. This issue has been filed under #579448.
When the discovery is completed, Drupal has the following information:
- The OP Endpoint URL to use during authentication requests
- The protocol version in use
If the user did NOT enter an OP Identifier (ie, not yahoo.com, but heine.myopenid.com), additional information is present:
- Claimed Identifier (openid.claimed_id)
- OP-Local Identifier (openid.identity)
These Identifiers should be used when doing authentication requests (Step 4 in the image), EXCEPT in the case of an OP Identifier. Then Drupal should use the the special URL "http://specs.openid.net/auth/2.0/identifier_select" as BOTH the Claimed Identifier (openid.claimed_id) and the OP-Local Identifier (openid.identity).
The vulnerability stems from the fact that Drupal doesn't use this URL for the Claimed Identifier during authentication, causing undefined behaviour in the OP.
To be continued…
That's all for now; steps 4-7 will have to wait for Part II.
Comments
Thank you for your research
Submitted by alex_b (not verified) on Tue, 2010/01/12 - 20:23Heine - thank you for this detailed writeup. I'll see you in the issue queue. Looking forward to subsequent posts.