There is a pretty good story behind this. That is, how we found and managed the OAuth protocol security threat identified last week. In many ways, the story is much more important and interesting than the actual technical details of the exploit.
For everyone involved, this was a first-of-a-kind experience: managing a specification security hole (as opposed to a software bug) in an open specification, with an open community, and no clear governance model. Where do you even begin?
But right now, I know you want the technical details.
If you are reading this, I assume you have a basic understanding of how OAuth works. If you don’t please take a few minutes to read at least the first two parts of my Beginner’s Guide to OAuth.
The first part will give you a general overview and second will take you through the user workflow. It is the workflow that is important here. The rest of the guide deals with security and signatures, but the content of these posts, surprisingly, is not involved in this attack.
I’ll start with what this attack is not about; it does not involve stealing usernames or passwords; it does not involve stealing tokens, secrets, or Consumer Keys; in fact, no part of the OAuth signature workflow is involved. This is not a cryptographic attack. And it does not violate the principal of a user granting access to a specific application. All that remains intact.
You might think I am stalling, but it is critical for people to understand both what is broken, as well as what is still secure (that is, no known exploits identified). It is important because even after this is made public, there will be plenty of OAuth-powered services up and running and I want you to feel safe using them.
Understanding the exact details of security threats is important in order to prevent exploits and fix the specification. But it is equally important to know what is working well and can be trusted.
For example, many applications use OAuth for 2-legged requests that do not involve user authorization and are unaffected by this threat. As a community effort, a big part of our work is to build adoption and support for the protocol. We need to fully disclose threats, and also make sure the reputation of the protocol is not damaged by misunderstandings and speculations.
The identified threat is a session fixation attack, empowered by a social engineering attack. The basic idea is that an attacker tricks an application using an OAuth API (a Consumer) to give it access to someone else’s resources (via an Access Token). The attacker never gets the Access Token itself, just the ability to use the application. But since the application has the Access Token, the attacker can use it to access the victim’s resources.
The OAuth authorization workflow consists of three steps:
- The user initiates the flow by requesting the application to access his resources. This triggers the application to request a Request Token from the provider and redirect the user to the provider for authorization.
- The user signs into the provider and grants access to the application. If the user is educated about phishing attacks, he can verify that he is in fact giving his password to the provider and no one else.
- After granting access, the user is redirected back to the application to complete the flow and enable it access to his data.
A normal OAuth Flow looks like this:
An attacker starts the process by using an application account and asking to initiate the flow, which results in the application obtaining a Request Token and redirecting it to the provider. At this point, the attacker stops and takes note of the Request Token included in the redirection URI. The attacker may also modify the callback but we will get to that later.
The attacker then uses social engineering to trick a victim into following that link (the authorization URI from the redirection). This can be as simple as a blog post with a review of the application, inviting people to try it out. When someone clicks on that link, they are sent to the provider to authorize access.
Since this is what he wanted to do, the victim will not realize that he should have started at the application itself, and will continue to sign into the provider. Because of how we train people to look for phishing attacks, even an educated user will notice that he is at the right place.
The provider will then ask the user to grant access, identifying the right application. This will increase the comfort level of the user, since so far, everything checks out. The right provider and the right application. But as soon as the user grants access, the attacker can construct the callback URI (from either learning how the application works or from the callback parameter provided in the redirection URI) and return to the application.
At this point, the attacker’s application account is associated with a valid and authorized victim Access Token. Remember: the attacker does not have the Access Token or any of the secrets. It was never in possession of the Request Token secret, and it does not make signed OAuth requests. All it has is an application account associated with the victim’s resources.
The Attack Flow looks like this (simplified):
Let’s talk about callbacks a bit. OAuth includes an optional callback parameter allowing Consumers to set a callback when they send the user to the provider for authorization. Providers can implement callback support in a couple of ways:
- Allow any callback at all.
- Allow any callback within a pre-registered (and verified) domain.
- Allow only a static pre-registered callback and ignore the parameter.
- Not allow callbacks at all, requiring manual user action.
If the provider uses the first option, the attacker can simply change the callback. Remember, the authorization step is not signed, allowing the attacker to change the callback parameter. This will send the user to a page controlled by the attacker, letting it know when it can go back to the application and finish the job.
If the provider uses the second or third option, this becomes a race condition as to who will make it back to the application first: the attacker or the victim. Now, since the attacker has no way of knowing exactly when the user grants access, it will have to try multiple times until it works. It is up to the Consumer application to handle such brute force attacks properly by voiding the entire transaction. But this requires the application to expect and handle this case.
Another point to consider in the second option is that sometimes domains have an open redirector which will redirect a request based on a given parameter. For example, this is done to count how many people click on ads or outbound links. An attacker can use such redirectors to set the callback to a URI at the pre-registered domain, which in turn will send it to an attacker controlled site.
If the provider uses the fourth option, it is similar to the previous two, but the race condition becomes a lot easier since it will take the victim longer to go back to the application (no automatic redirection back). Also, in manual ‘press here when done’ cases, applications tend to allow multiple attempts because users make more mistakes. In such cases, the application will simply try to exchange the Request Token with an Access Token and will fail until the access is authorized by the user. If the provider doesn’t void the transaction on the first failed attempt, it increases the exposure.
Now, we talked about the attack and we talked about callbacks. But that is not all.
Even if the attacker doesn’t get to be first to return to the application or find out when access has been granted, the application may have a critical design flaw. Some applications use the identity of the user who started the flow when they associate the Access Token with the local account. What this means is that even if the victim is the one to return to the application, the application will still attach his Access Token to the attacker account because it will look up the Request Token in the database and use that information to continue.
If we put it all together, even if an application:
- Makes sure to use the account of the user coming back from the callback and not that of the one who started
- Checks that the user who starts the process is the same as the one who finishes it
- Uses an immutable callback set at registration
It is still exposed to a timing attack trying to slide in between the victim authorizing access and his browser making the redirection call. There is simply no way to know that the person who authorizes is the same person coming back. None whatsoever.
And that’s pretty much it.
To get you started thinking about a solution, a few community members are thinking about fixing this by adding an unpredictable callback parameter generated at the Service Provider to the callback URI on return. The Consumer will need this new extra parameter to exchange the Request Token for an Access Token, ensuring that the real user has to return to the application to complete the flow.
We’ll meet back here tomorrow to continue talking about ideas on how to fix it with a protocol revision. Meanwhile, think about this for a bit, join the conversation on the OAuth mailing list, talk about it with your coworkers, or blog your thoughts.
Brian Eaton, Dirk Balfanz, and Chris Messina provided valuable feedback to this post.