1. Introduction

OAuth is the industry standard framework for delegated authorization. A lot of thought and care has gone into creating the various flows that make up the standard. Even then, it’s not without vulnerability.

In this series of articles, we’ll discuss attacks against OAuth from a theoretical standpoint and describe various options that exist to protect our applications.

2. The Authorization Code Grant

The Authorization Code Grant flow is the default flow that is used by most applications implementing delegated authorization.

Before that flow begins, the Client must have pre-registered with the Authorization Server, and during this process, it must have also provided a redirection URL — that is, a URL on which the Authorization Server can call back into the Client with an Authorization Code.

Let’s take a closer look at how it works and what some of these terms mean.

During an Authorization Code Grant Flow, a Client (the application that is requesting delegated authorization) redirects the Resource Owner (user) to an Authorization Server (for example, Login with Google). After login, the Authorization Server redirects back to the client with an Authorization Code.

Next, the client calls into an endpoint at the Authorization Server, requesting an Access Token by providing the Authorization Code. At this point, the flow ends, and the client can use the token to access resources protected by the Authorization Server.

Now, the OAuth 2.0 Framework allows for these Clients to be public, say in scenarios where the Client can’t safely hold onto a Client Secret. Let’s take a look at some redirection attacks that are possible against Public Clients.

3. Redirection Attacks

3.1. Attack Preconditions

Redirection attacks rely on the fact that the OAuth standard doesn’t fully describe the extent to which this redirect URL must be specified. This is by design.

This allows some implementations of the OAuth protocol to allow for a partial redirect URL.

For example, if we register a Client ID and a Client Redirect URL with the following wildcard-based match against an Authorization Server:

*.cloudapp.net

This would be valid for:

app.cloudapp.net

but also for:

evil.cloudapp.net

We’ve selected the cloudapp.net domain on purpose, as this is a real location where we can host OAuth-powered applications. The domain is a part of Microsoft’s Windows Azure platform and allows any developer to host a subdomain under it to test an application. This in itself is not a problem, but it’s a vital part of the greater exploit.

The second part of this exploit is an Authorization Server that allows wildcard matching on callback URLs.

Finally, to realize this exploit, the application developer needs to register with the Authorization server to accept any URL under the main domain, in the form *.cloudapp.net.

3.2. The Attack

When these conditions are met, the attacker then needs to trick the user into launching a page from the subdomain under his control, by for example, sending the user an authentic looking email asking him to take some action on the account protected by OAuth. Typically, this would look something like https://evil.cloudapp.net/login. When the user opens this link and selects login, he will be redirected to the Authorization Server with an authorization request:

GET /authorize?response_type=code&client_id={apps-client-id}&state={state}&redirect_uri=https%3A%2F%2Fevil.cloudapp.net%2Fcb HTTP/1.1

While this may look typical, this URL is malicious. See, in this case, the Authorization Server receives a doctored URL with the app’s Client ID and a redirection URL pointing back to evil’s app.

The Authorization Server will then validate the URL, which is a subdomain under the specified main domain. Since the Authorization Server believes that the request originated from a valid source, it will authenticate the user and then ask for consent as it would do normally.

After this is done, it will now redirect back into the evil.cloudapp.net subdomain, handing the Authorization Code to the attacker.

Since the attacker now has the Authorization Code, all he needs to do is to call the token endpoint of the Authorization Server with the Authorization Code to receive a token, which allows him access to the Resource Owner’s protected resources.

4. Spring OAuth Authorization Server Vulnerability Assessment

Let’s take a look at a simple Spring OAuth Authorization Server configuration:

@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {    
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
          .withClient("apricot-client-id")
          .authorizedGrantTypes("authorization_code")
          .scopes("scope1", "scope2")
          .redirectUris("https://app.cloudapp.net/oauth");
    }
    // ...
}

We can see here that the Authorization Server is configuring a new client with the id “apricot-client-id”. There is no client secret, so this is a Public Client.

Our security ears should perk up at this, as we now have two out of the three conditions – evil people can register subdomains and we are using a Public Client.

But, note that we are configuring the redirect URL here too and that it’s absolute. We can mitigate the vulnerability by doing so.

4.1. Strict

By default, Spring OAuth allows a certain degree of flexibility in redirect URL matching.

For example, the DefaultRedirectResolver supports subdomain matching.

Let’s only use what we need. And if we can just exactly match the redirect URL, we should do:

@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {    
    //...

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.redirectResolver(new ExactMatchRedirectResolver());
    }
}

In this case, we’ve switched to using the ExactMatchRedirectResolver for redirect URLs. This resolver does an exact string match, without parsing the redirect URL in any way. This makes its behavior far more secure and certain.

4.2. Lenient

We can find the default code that deals with redirect URL matching in the Spring Security OAuth source:

/**
Whether the requested redirect URI "matches" the specified redirect URI. For a URL, this implementation tests if
the user requested redirect starts with the registered redirect, so it would have the same host and root path if
it is an HTTP URL. The port, userinfo, query params also matched. Request redirect uri path can include
additional parameters which are ignored for the match
<p>
For other (non-URL) cases, such as for some implicit clients, the redirect_uri must be an exact match.
@param requestedRedirect The requested redirect URI.
@param redirectUri The registered redirect URI.
@return Whether the requested redirect URI "matches" the specified redirect URI.
*/
protected boolean redirectMatches(String requestedRedirect, String redirectUri) {
   UriComponents requestedRedirectUri = UriComponentsBuilder.fromUriString(requestedRedirect).build();
   UriComponents registeredRedirectUri = UriComponentsBuilder.fromUriString(redirectUri).build();
   boolean schemeMatch = isEqual(registeredRedirectUri.getScheme(), requestedRedirectUri.getScheme());
   boolean userInfoMatch = isEqual(registeredRedirectUri.getUserInfo(), requestedRedirectUri.getUserInfo());
   boolean hostMatch = hostMatches(registeredRedirectUri.getHost(), requestedRedirectUri.getHost());
   boolean portMatch = matchPorts ? registeredRedirectUri.getPort() == requestedRedirectUri.getPort() : true;
   boolean pathMatch = isEqual(registeredRedirectUri.getPath(),
     StringUtils.cleanPath(requestedRedirectUri.getPath()));
   boolean queryParamMatch = matchQueryParams(registeredRedirectUri.getQueryParams(),
     requestedRedirectUri.getQueryParams());

   return schemeMatch && userInfoMatch && hostMatch && portMatch && pathMatch && queryParamMatch;
}

We can see that the URL matching is done by parsing the incoming redirect URL into its component parts. This is quite complex due to its several features, like whether the port, subdomain, and query parameters should match. And choosing to allow subdomain matches is something to think twice about.

Of course, this flexibility is there, if we need it – let’s just use it with caution.

5. Implicit Flow Redirect Attacks

To be clear, the Implicit Flow isn’t recommended. It’s much better to use the Authorization Code Grant flow with additional security provided by PKCE. That said, let’s take a look at how a redirect attack manifests with the implicit flow.

A redirect attack against an implicit flow would follow the same basic outline as we’ve seen above. The main difference is that the attacker gets the token immediately, as there is no authorization code exchange step.

As before, an absolute matching of the redirect URL will mitigate this class of attack as well.

Furthermore, we can find that the implicit flow contains another related vulnerability. An attacker can use a client as an open redirector and get it to reattach fragments.

The attack begins as before, with an attacker getting the user to visit a page under the attacker’s control, for example, https://evil.cloudapp.net/info. The page is crafted to initiate an authorization request as before. However, it now includes a redirect URL:

GET /authorize?response_type=token&client_id=ABCD&state=xyz&redirect_uri=https%3A%2F%2Fapp.cloudapp.net%2Fcb%26redirect_to
%253Dhttps%253A%252F%252Fevil.cloudapp.net%252Fcb HTTP/1.1

The redirect_to https://evil.cloudapp.net is setting up the Authorization Endpoint to redirect the token to a domain under the attacker’s control. The authorization server will now first redirect to the actual app site:

Location: https://app.cloudapp.net/cb?redirect_to%3Dhttps%3A%2F%2Fevil.cloudapp.net%2Fcb#access_token=LdKgJIfEWR34aslkf&...

When this request arrives at the open redirector, it will extract the redirect URL evil.cloudapp.net and then redirect to the attacker’s site:

https://evil.cloudapp.net/cb#access_token=LdKgJIfEWR34aslkf&...

Absolute URL matching will mitigate this attack, too.

6. Summary

In this article, we’ve discussed a class of attacks against the OAuth protocol that are based on redirection URLs.

While this has potentially serious consequences, using absolute URL matching at the Authorization Server mitigates this class of attack.