Tuesday, January 6, 2015

Attacking SSO Part 2: Breaking OpenID in Drupal with Key Confusion

In this Post, we will describe a vulnerability in Drupal's OpenID SSO module that was shipped with Drupal Core prior Versions 6.30 and 7.26. The attack allows an attacker to login as an arbitrary user (even as an Admin), but does not require any interaction with the victim. The vulnerability was reported to the Drupal Security Team and they fixed it at the beginning of 2014 (SA-CORE-2014-001).
To detect the vulnerability, we developed a novel SSO attack technique called Key Confusion. We discovered the attack by setting up our own IdP for analyzing and attacking SSO, see Part 1 of our SSO attack series.


Key Confusion Attack on SSO

 

In Single Sign-On protocols like OpenID, OpenID Connect, SAML or BrowserID, the SSO token that is transferred from the IdP to the SP via the user's browser is cryptographically protected. In the case of OpenID, a HMAC is used.
Because decentralized SSO consists of multiple IdPs, the question that raises is:
How does the SP pick the right key to verify the token?
The basic idea of the Key Confusion attack is to let the SP use a key to verify the token, which SP believes to belong to honest Party (e.g. Google, Yahoo), while in fact, this key belongs to the attacker. The attacker has therfore to setup his own IdP and we refer to it as “malicious IdP”.
The following figure depicts the concept:

Figure 1: Key Confusion concept.


  1. The attacker starts the OpenID login procedure by entering his own URL.ID=attacker.
  2. The SP gathers the URL.IdP of the attacker IdP by discovering URL.ID=attacker. The attacker uses his malicious IdP (i.e. the IdP is controlled by the attacker) to establish a key β with the SP. This takes place in the association phase of OpenID, see our blogpost on the OpenID protocol description and is conform to the normal OpenID protocol flow.
  3. The SP stores the established key in its database. It uses the Key ID β in this example. Note that in OpenID, the Key ID value is defined by the (malicious) IdP. That means, it could be possible for a malicious IdP to overwrite existing key material. For the attack on Drupal as described on this post, the overwriting of key material is not needed.
  4. The attacker sends an SSO token to the SP. For simplicity reasons, we leave out the token creation here and some of the protocol messages are omitted in the Figure shown above, e.g. the initial request sent from the SP to the IdP and the authentication of the user.
  5. The SP proceeds with the verification of the token's signature σ. It therefore needs to fetch the key with Key ID β as requested in the token. Because of the value URL.IdP=victim contained in the token, the SP believes that the key β belongs to the victim's IdP, but actually it belongs to the malicious IdP (see Step 1).
At the end, the attacker is logged in at the SP with URL.ID=victim.



Key Confusion: Summary


Goal:
  • SP chooses the wrong key to verify the signature in the SSO token.
  • The SP permits the access to victim's account.



Attack:
  • The attacker sends token t = (URL.ID=victim, URL.IdP=victim, β), where β belongs to a key controlled by URL.IdP=attacker (instead of URL.IdP=victim).



Breaking OpenID in Drupal... The Code


We applied the above attack concept on Drupal's OpenID implementation. However Drupal detects the attack. So we had a deeper look at Drupal's OpenID code (the code is simplified for readability, but can be found in the File modules/openid/openid.module):

01. if (openid_verify_assertion($service, $response)) {
02. if ($response['openid.claimed_id'] != $service['openid.claimed_id') {
03. $discovery = openid_discovery($response['openid.claimed_id']);
04. return openid_idp_uri_in_discovery($discovery, $service['openid.op_endpoint']);
05. }
06. }

First, a short explanation of what variables are used in this code:
  • $response contains the OpenID token that is sent to Drupal (cf. Step 4 in Figure 1).
  • $service holds information about the discovered URL.ID that was sent in Step 1, Figure 1.
Back to the code:
Line 1 verifies the token. This includes the token's signature verification. If the verification succeeds, Line 2 checks if the URL.ID parameter contained in the response matches the URL.ID parameter that was previously requested (Step 1, Figure 1). If this is not the case, a rediscovery is started in Line 3 on the URL.ID parameter in the response. Finally, Line 4 verifies if the discovered document at URL.ID contains the matching URL.IdP.
We started the Key Confusion attack. First, we sent to URL.ID=attacker Drupal as a login request. Drupal discovers URL.ID=attacker and associates a key β with our malicious IdP. We then sent a token t = (URL.ID=victim, URL.IdP=victim, β) to Drupal. Using the debugger, we observed that the token was successfully verified (Line 1 passed), that means, Drupal uses the key identified β. However Drupal noticed that the claimed identifier (URL.ID=victim) in the token t does not match the previously requested identifier (URL.ID=attacker). Thus, Drupal starts the rediscovery (Lines 2-3) on URL.ID=victim. Drupal fetches the URL.IdP= victim in the discovery. So what Line 4 basically does, is the following: It compares if URL.IdP= victim (Parameter: $discovery) matches URL.IdP=attacker (Parameter: $service['openid.op_endpoint']).
Note that in Line 4, Drupal does not use $response['openid.op_endpoint'] (the URL.IdP parameter contained in t) but $service['openid.op_endpoint'] (the URL.IdP parameter stored after the login request).
The main question to answer was: What exactly is $service and can we manipulate it?
We again found the answer in the Code (File: modules/openid/openid.module):
338 $service = $_SESSION['openid']['service'];
The variable value is taken from the PHP Session. We looked deeper into the code, and found out, that $_SESSION['openid']['service'] is initialized on Step 1. of our attack (cf. Attack Concept Figure).

Breaking OpenID in Drupal... The Exploit

 

Using the knowledge of the code we earned above, we created an exploit that is depicted in the following Figure:

Key Confusion Attack on Drupal.


  • Step 1-3: The attacker starts to login on Drupal. He therefore submits his own attacker identity URL.IDA. The SP then starts the discovery on URL.IDA to determine URL.IdPA.
    • Drupal stores $_SESSION[URL.ID] = URL.IDA and $_SESSION[URL.IdP] = URL.IdPA as session state.
  • Step 4: Drupal associates a key with URL.IdPA. The key is stored with association handle β.
  • Steps 5-7: Drupal redirects the attacker to URL.IdPA. The malicious IdP creates a token t = (URL.IDV , URL.IdPV , URL.SP, β).
    • The attacker delays here and does not proceed sending the token back to SP.
  • Steps 8-10: The attacker submits a further login request to Drupal, but this time with the victim’s identity URL.IDV. Drupal starts a new discovery on it and receives URL.IdPV. Both values, URL.IDV and URL.IdPV, are then stored in $_SESSION, overwriting URL.IDA and URL.IdPA .
  • Step 11: Drupal starts another association. This time with URL.IdPV. This association is stored with the association handle α.
  • Step 12: Drupal redirects the attacker to URL.IdPV. The attacker stops here, because he does not know the credentials to login at URL.IdPV. His goal is reached, the $_SESSION variables are overwritten.
  • Step 13-14: The attacker continues to forward Step 7 to the Drupal SP. Drupal verifies the signature σ using association handle β from t*. The signature is valid, and Drupal continues with the code outlined above. Because $_SESSION[URL.ID] is equal to $response[URL.ID], Drupal does not start a rediscovery.
At the End, the attacker is logged in with URL.IDV.



Conclusion



What can we learn from this attack?


A proper key management should always store the key's origin


Drupal fetches (and stores) the key material as follows:
$association = db_query("SELECT * FROM {openid_association} WHERE assoc_handle = :assoc_handle", array(':assoc_handle' => $response['openid.assoc_handle']))->fetchObject();
The problem with the code above is, that the key is only fetched by the variable openid.assoc_handle. This value does not include for which IdP this can must be used. A better way to store key material would be:
$association = db_query("SELECT * FROM {openid_association} WHERE assoc_handle = :assoc_handle AND url_idp = :url_idp", array(':assoc_handle' => $response['openid.assoc_handle'], ':url_idp' => $response['openid.op_endpoint]))->fetchObject();
This method connects both, the key identifier and the origin. After contacting the Drupal Security Team, they applied the behavior above and Key Confusion was no longer possible.

Connecting HTTP requests via Session variables can cause problems


A concept that the attack on Drupal reveals, is that the use of variables stored as session parameters cannot be handled as trustworthy input for cryptographic computations. HTTP by itself is a stateless protocol and the use of cookies/sessions are the concept of connecting a sequence of requests. However, when it comes to security, one should not rely on these parameters. If, for example, Drupal had started a rediscovery every time, the attack would not be possible.
An interesting fact on our Key Confusion attack is related to our previous post that attacks OpenID on Sourceforge with ID Spoofing. The Sourceforge Security Team fixed the ID Spoofing vulnerability, but we then tried out the Key Confusion Attack in the same manner as on Drupal. Sourceforge was vulnerable. We then contacted the Sourceforge Security Team again, and they fixed the issue on a similar way as Drupal.

Authors of this Post

Vladislav Mladenov
Christian Mainka (
@CheariX)

Beliebte Posts