Update: three years later I wrote something new… introducing Oz.
My last post about the lack of signature support in OAuth 2.0 stirred some very good discussions and showed wide support for including a signature mechanism in OAuth 2.0. The discussions on the working group focused on the best way to include signature support, and a bit on the different approached to signatures (more on that in another post).
However, the discussion failed to highlight the fundamental problem with supporting bearer tokens at all. In my previous post I suggested that bearer tokens over HTTPS are fine for now. I’d like to take that back and explain why OAuth bearer tokens are a really bad idea.
But first, some live entertainment:
Facebook developer ‘wesbos’ writes:
Hey guys, I’m using the freshly downloaded PHP API
I’ve got eveything setup and when I login to authenticate, I get this error:
Fatal error: Uncaught CurlException: 60: SSL certificate problem, verify that the CA cert is OK. Details: error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed thrown in /Users/wesbos/Dropbox/OrangeRhinoMedia/reporting/src/facebook.php on line 512
To which ‘Telemako’ replies (emphasis is mine):
[…] try this one:
$opts[CURLOPT_SSL_VERIFYPEER] = false;
$opts[CURLOPT_SSL_VERIFYHOST] = 2;
And ‘philfreo’ asks:
Is there any good reason why these shouldn’t be a part of the core default $CURL_OPTS?
Well, philfreo, Telemako, and wesbos, you have just turned off one of the most important protections SSL provides: defense against MITM attacks.
Putting All Your Eggs in One Basket
The main problem with using OAuth bearer tokens is the fact that they rely solely on SSL/TLS for its security. Bearer tokens have no internal protections. Just like cash, whoever holds the token is its rightful owner, and proving otherwise is impractical. OAuth 1.0 signatures evolved from the basic requirement not to mandate SSL/TLS. But using signatures goes beyond just a deployment consideration.
Consider the utility of a safety parachute. A determined attacker trying to kill you will obviously sabotage the safety parachute just as easily as he can sabotage the primary one. So, does that mean you might as well jump without a safety parachute? Of course not. You want to take into account not just the worst-case attacker, you want to take into account your own stupidity. A safety parachute means that, if you packed your primary wrong, you can still live. Defense in depth, as it’s more commonly known in the security community, is usually not about building the 12 layers of security around the “Die Hard” vault that a skilled attacker has to vanquish, one by one. Defense in depth is the humble realization that, of all the security measures you implement, a few will fail because of your own stupidity. It’s good to have a few backups, just in case.
No matter what you do, if for any reason the SSL/TLS layer is broken, the entire security architecture of OAuth bearer tokens falls apart. In a world moving steadily towards multiple means of authentication and verification, this is a step backwards.
SSL/TLS is Harder than You Think
It is common knowledge among security experts that implementing SSL/TLS is notoriously hard. This is why very few attempt to write their own libraries. However, equally well established is that many clients implement SSL/TLS in ways that void its most valuable defense: that against a man-in-the-middle. If anyone can get between your client and the server, they can capture the token and use it at will.
The example above shows one of two common ways in which SSL/TLS security is compromised. I would love to mock the developers quoted that was a mistake I made myself three years ago when writing my own OpenID library. I had no problem getting the signatures done, but had a hard time with bad certificate errors. Some were due to local configuration (getting root certificates configured is still a hard thing for most developers to do), but also due to poorly configured servers. My solution, just like the suggestion above, was to ignore these errors.
The problem with SSL/TLS, is that when you fail to verify the certificate on the client side, the connection still works. Any time ignoring an error leads to success, developers are going to do just that. The server has no way of enforcing certificate verification, and even if it could, an attacker will surely not.
This is not new. Ben Adida described this exact issue when reviewing the original WRAP proposal:
But wait, you say, don’t worry, the token is sent over SSL, so it’s all good.
Right. What’s going to happen when someone “forgets to turn on SSL”, which is all too common when security is abstracted out “somewhere down in the stack.” Or when we stop dealing with those pesky certificate errors and just choose not to validate the cert, which will leave the protocol wide open to network attackers who can now literally play man-in-the-middle just by spoofing DNS on a wifi network, capturing the token, and replaying it to access all sorts of additional resources, effectively stealing the user’s credentials.
This might actually be worse than passwords, because at least you can work to educate users about SSL (and after their Facebook account gets hacked, they might actually care), but it’s very hard for users to gauge whether web applications are doing the right thing with respect to SSL certs when the SSL calls are all made by the backend which has trouble surfacing certificate errors.
The second common potential problem are typos. Would you consider it a proper design when omitting one character (the ‘s’ in ‘https’) voids the entire security of the token? Or perhaps sending the request (over a valid and verified SSL/TLS connection) to the wrong destination (say ‘http://gacebook.com’?). Remember, being able to use OAuth bearer tokens from the command line was clearly a use case bearer tokens advocates promoted.
Who is in Charge of Your Service Security?
The main difference between using bearer tokens and signatures is in who they put in charge of enforcing the service security. Signatures ensure that mistakes don’t compromise the token because the secret is never sent with the request. At most, an attacker can use the captured request once, and for enhanced security, signatures can and should be used together with SSL/TLS for a multi-layer protection.
Yes, developers do stupid things, like posting their client credentials (API keys) on public lists and in open source code. But I have yet to see developers expose token secrets like that. It is really hard to expose OAuth 1.0a token secrets by mistake (unless that mistake is leaving your entire database open for public review, in which case, nothing will help you).
Bearer Tokens are not Just Like Cookies
The winning argument in favor of using bearer tokens has always been cookies. Bearer tokens have the same security properties of cookie authentication, as both use plaintext strings without secrets or signatures. However, there is one big difference – the client developer.
The Ironic Solution
We know developers don’t read security considerations sections. That’s a fact of life. Developer only read the parts they need to implement and ignore the parts that ask them to think. Only large companies with dedicated security teams pay much attention to that, and they usually do their own analysis. So warning developers to check the certificate is not likely to accomplish much.
What’s the alternative? SDKs!
If your developers cannot be trusted to implement your API correctly, it is common to offer them a library instead. I hope you can see the irony here. If you solve the SSL/TLS deployment issue with an SDK, why not just implement signatures? One argument used against signatures was that even with libraries, people will try to write their own code. Well, that’s a scary proposition.
Security is Hard
Ben Laurie, one of Google’s top security experts and the OpenSSL project lead writes in his explanation why WRAP “is just so obviously a bad idea, it’s difficult to know where to start”:
The idea that security protocols should be so simple than anyone can implement them is attractive, but as we’ve seen, wrong. But does the difficulty of implementing them mean they can’t be used? Of course not […] every crypto algorithm under the sun is hard to implement, but there’s no shortage of libraries for them, either.
As I have pointed out before, the ‘signatures are hard’ argument dies with Twitter’s recent deployment of OAuth signatures as a requirement.
But Everyone Else is Doing it
My favorite argument in favor of bearer tokens, and the most ridiculous one, is that Facebook, Google, Microsoft, and Yahoo! are all doing it, so that must mean its good. After all, they must have reviewed it and decided it was safe.
The other variation of this argument is that bearer tokens are already widely deployed so we need to support them. All I have to say is quote the obvious: “if your friend jumped off a roof, would you?”
Facebook is using MD5 in their current API, probably because that is what Flickr used when Facebook copied their API authentication scheme. See the pattern? New services simply follow what other established companies are already doing, without questioning the reasoning behind it. Big companies have resources to make these decisions, but they rarely apply as-is to others, and it is simply irresponsible to ignore their leadership position.
This is how we got stuck with Basic authentication and cookie logins. This is what RFC 2617 has to say about Basic HTTP authentication:
The Basic authentication scheme is not a secure method of user authentication, nor does it in any way protect the entity, which is transmitted in cleartext across the physical network used as the carrier.
And yet, it is still one of the most widely deployed authentication scheme on the web. People don’t read specifications, they just follow the market.
Actually, Not Everyone is Doing it
OAuth 1.0 had bearer token support alongside signatures for three years now, and yet, it is barely used. Twitter could have deployed OAuth 1.0 as specified in RFC 5849 section 3.4.4 but chose not to. The claim that bearer tokens are a new feature is false. All OAuth 2.0 does is clean it up and present it in a more accessible way.
The problem is both with the specification and the market hype. By positioning bearer tokens as the default choice for 2.0 developers, and by highlighting the big vendors deploying it, developers are less likely to think for themselves, especially given the allergic reaction many developers have to signatures.
I still have hope that early and future adopters of OAuth 2.0 will make the right decision and not deploy bearer token support for their core APIs. I have never suggested to outright ban bearer tokens. They provide a useful way to experiment with an API, to try out ideas, to test code, and to make API calls that have little to no security requirements. For example, it is perfectly fine to use bearer tokens for any Twitter API call that returns user-specific data that is publically available (like your followers list). Not so fine to use it to update your status or read private messages.
I have concluded that the OAuth working group is not a productive place to have this debate. People are much too entrenched in their positions and with existing live deployments, a committee is not the way to reach consensus. Instead, I believe that an open public debate and market pressure is the best way to go. This is where I hope others will join me in asking their service providers to take their security more seriously.
With regard to the specification, I have proposed (and so far received wide support) to make an editorial change that will separate the ‘how to get a token’ part from the ‘how to use a token’ part. This is not a new idea. By doing so, we will enhance the modularity of the protocol and remove any bias for or against bearer tokens and signatures from the specification. Instead, developers will be presented with two or more alternatives, and will have to make their own decision about what is right for their platform. This proposal is still pending working group consensus.
I am sure this will not be the last word on the subject.