Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
OAuth 2.0 Authentication Vulnerabilities (portswigger.net)
174 points by lobo_tuerto on March 27, 2021 | hide | past | favorite | 67 comments


The first vulnerability is in the title, OAuth is an Authorization framework (Open Authorization) and is explicitly NOT for authentication. It’s also a delegation protocol (I give you something to do on my behalf).

If you want a list of things that can go wrong, look here: https://tools.ietf.org/id/draft-ietf-oauth-security-topics-1...

Generally you probably do not need OAuth2: https://www.ory.sh/hydra/docs/concepts/before-oauth2/

But if you do don’t roll your own but use proven open source like https://github.com/ory/hydra


From the article:

> OAuth authentication

> Although not originally intended for this purpose, OAuth has evolved into a means of authenticating users as well.


The OAuth 2.0 RFC specifies the 'password' grant type, where the user provides the username and password directly.

I'd contest the claim that OAuth was not intended for authentication, because there are no authz uses for the password grant.


It famously wasn't intended for authentication, which is why OIDC was developed on top of it. Trying to run a delegated authorization protocol as an authentication protocol caused vulnerabilities.

There are obvious authz uses for the password grant: you use it when you want to delegate access to a client running on your desktop, which is in your custody, and there's no point in running a multi-legged authorization protocol because you can just log the client in yourself. Your first thought about that might be "that's authentication", but it's not: you don't have to give all-or-nothing access (in theory) to such a client.


OAuth 2.0 password grant can be (mis)used for authentication the same way that LDAP Bind is used for authentication. That doesn't make either of them an authentication protocol.


I've been seeing more and more apps and websites have me login with my xyz app login info. Then, ask if it's ok to grant access to xyz app so it can access xyz app information. Makes sense only if you are a developer. You are granting the frontend access to the backend via oauth. Completely confusing for everyone else and was pretty confusing for me at first as well.


In fact, there id say that the huge majority of oauth flows on the web are used for authentication at this point.


If you mean, like, logging into things with Google, sure, but isn't that technically OIDC? If you mean to say "most OAuth is used for OIDC, and is thus authentication", that's a different and less interesting claim. If instead you're saying that vanilla OAuth is primarily used for authentication, you're saying something more interesting (and problematic). You can use vanilla OAuth to log in, but you're adding a particularly subtle class of possible flaws in your design by doing so.


I mean the former. The primary use case of oauth on the modern web is to support openid connect. So much so that I expect it'll be a "SSL vs TLS" thing in the future where we actually use "oauth" to refer to the entire openid connect flow.


Sure, OK. But this article really thinking about OAuth authentication in terms of OAuth itself, not OIDC. The dominant use of TCP on the Internet is (I hope?) to fetch URLs, but HTTP is not TCP. :)


To be more precise, the flows are used to initiate authentication. They do not actually perform it.


Counterpoint: although SSN was not intended as a means of identification, it evolved into being used that way.


I'm currently trying to decide if OAuth is overkill for something I'm working on: a first-party browser extension for a SaaS. The extension needs to authenticate the user with the SaaS on installation, then make API calls to the SaaS on the user's behalf. In theory, OAuth is a good idea because it's a standard, as opposed to some ad-hoc system that I cook up myself. But if I try to use an off-the-shelf OAuth provider implementation in the SaaS, it's obvious that I'm not using it for its intended purpose, because when a user goes through the OAuth authorization flow, they get a screen asking whether they authorize the app to access the service. But in the user's mind, the app (the browser extension) is part of the service. So, does that just mean I need to tweak the OAuth provider? Or is this a hint that I should go with a simpler solution?


You can use HMAC [0]. Create a UI to collect username/password and make an API call to login endpoint, which should return sessionId/secret. going forward sign API requests using the HMAC protocol without ever revealing the secret on the wire again.

0: https://aspsecuritykit.net/guides/implementing-hmac-scheme-t...


OAuth is overkill for what you're working on. You generally won't go wrong with the rule of thumb that you do the least amount of cryptography your design requires you to. You should be dragged kicking and screaming into more of it. In this case, generating a long random token and using that to authorize requests has all the basic security benefits of an OAuth bearer token and almost none of the attack surface. Long random tokens are the most overrated technology in authentication.

A general rule of thumb w/r/t/ OAuth is that it starts to make sense when you are delegating authorization to other companies that share your users. Think "TweetLater".


“Long random tokens are the most overrated technology in authentication.”

Just so I follow, did you mean underrated…?


Yeah, sorry!


But then you need to implement token revocation, by the host, the user or the client, and find a cryptographically-secure way to generate the key.


To revoke the token, the server deletes it from the database. If you have the token, you can ask the server to delete it from the database. The cryptographically secure way to generate it is to read 32 bytes from getrandom(2) or from /dev/urandom.


you basically told somebody to roll his own crypto. that is a stupid idea. using refresh tokens and access tokens in a standard way is way more secure than rolling your own, this stuff is already pretty hard. of course one could go with a simple cookie login, but when it comes to external apps, that's not always a good idea, especially not if you need to revoke specific applications.

so your general rule of thumb is pretty stupid.


In what way is getting 32 bytes from /dev/urandom rolling your own crypto?


So called "first party apps", those that are first party to the IDP, can often be considered as pre-consented. So the consent can be skipped - it's why you don't need to consent to use Excel or Gmail.


If you have no other features ever planned around authentication or authorization, and no other planned client software, you can build what you want in directly.

OAuth provides an abstraction between the authentication, permission grant and user consent and getting a token representing authorization, and provides additional best practices for things like securing web and native app access, token revocation, token introspection, etc. You probably want to be in the business of improving your SaaS, not designing a secure access system.

That isn't to say you can't build something now with the idea that you will migrate away from that once you hit an inflection point in complexity due to new features.


I don't think it's at all the case that OAuth builds baseline best practices regarding authentication into applications. OAuth makes --- in fact, essentially is --- a series of concessions to enable delegated authorization, which is a much harder problem than simple authentication or single-site authentication. When you use OAuth in simplistic scenarios, you're importing all the challenges (and, as this page, the RFC, and a zillion other sources show, vulnerabilities) that OAuth has to deal with to make delegation work... but for no reason.

I'd generally caution against using OAuth until you know you need it.


I think it make sense to have a oAuth service for it. Like you said, it standard and straight forward. If you will have third party consume your users API you won't have to put too much effort into coming up with a different scheme.

google and TDAmeritrade authenticate their 1st party services with oAuth and it logically makes sense to me.


Nest does this. When you auth with Google, it asks for consent, even though they are owned by same company.

Agree below though on preconsent.


In reality, most of the applications are using it for authentication: to identify who you are using your GitHub, FaceBook or Google account. Authorization is the process of determining whether the identity can perform the specified action on the specified data [0]; such social logins provide close to no real options to users to specify this aspect.

0: https://aspsecuritykit.net/guides/designing-activity-based-d...


It is designed for authorization but as sibling comments have said, it is very often used for authentication. Even though good old RFC 6749 says nothing about the details of authentication and leaves the nitty gritty of that to the Authorization server. But almost every OAuth server I'm aware of has some kind of authentication functionality.

100% agree that you should not roll your own. There are lots and lots of options out there with different strengths and weaknesses. Determine what you need and then find the right solution (which may be hydra or something else).


RFC 6749 does not contain any to verify tokens are usable for authentication, and is insecure (and has been exploited) when used for authentication on its own.

You have extensions like Facebook Connect or OpenID Connect which add on the additional technology and client steps to allow it to be used securely for authentication.

The title is wrong because those involved in the standardization of OAuth 2.0 have yelled from the very beginning not to use it for authentication, but instead use something that builds authentication on top of it.


Nice, that page and your post is basically an ad. You don't need a proven open standard, you need our custom protocol instead!


From a quick glance, it doesn't look like a protocol per se, but a user management system akin to Keycloak. Those solve a slightly different set of problems than OIDC or OAuth


It explains why OAuth2 is hard to use and does not solve login, registration, sessions, profile management, mfa, and proposes another solution. It’s all open source! :)


Interesting, I always thought it was the other way around.

It would authenticate who you are, but you have authorize the people yourself later.


OpenID Connect is built on top of OAuth2 and is the recommended solution for authentication.


Your first link calls the specification "Open Authentication" which seems to contradict your statement that it's called "Open Authorization".


That looks like a mistake in the doc:

- https://oauth.net/articles/authentication/

- https://tools.ietf.org/html/rfc6749 - The OAuth 2.0 Authorization Framework


-> OAuthz (vs authn)


Great post. I work in the space and am coming up to speed on some of the security issues.

Comments:

* this is a great read about security and OAuth: https://tools.ietf.org/html/draft-ietf-oauth-security-topics... written by experienced folks.

* don't use the implicit grant, please. Use the authorization code grant with PKCE. Here's an example (from a doc I helped write, talking about how bad the implicit grant is: https://fusionauth.io/learn/expert-advice/oauth/modern-guide...

* State can be used for CSRF protection but I've also seen it used to convey, well, state that needs to be carried over. That's legitimate, but make sure you append a random value as suggested.

* The well known endpoints will only be present if the OAuth server supports RFC 8414: https://tools.ietf.org/html/rfc8414 but you can always check the documentation. Most OAuth servers have plenty of public documentation to make their usage easier.

* Re "Flawed redirect_uri validation", the OAuth 2.1 spec has tightened this up some and wants exact URI matching. We have some customers who want wildcard matching, but the redirect_uri check is a fundamental part of the OAuth security architecture, so we've resisted that request.

* "However, by stealing an OAuth code or token, the attacker can gain access to the user's account in their own browser." is a good reminder that you should keep your token lifetimes short. We recommend minutes; use a refresh token to renew the token.

All in all, thought provoking article. There's a reason why more and more teams are outsourcing auth; there's a lot of flexibility in the specs and it's easy to make mistakes. Even when you do it right the first time, there's ongoing maintenance. You should run a bug bounty program and/or regularly pentest your system.


> * State can be used for CSRF protection but I've also seen it used to convey, well, state that needs to be carried over. That's legitimate, but make sure you append a random value as suggested.

Now that we are supposed to just use PKCE, we can stop using state for a nonce and CSRF protection and just use it for state.

> * "However, by stealing an OAuth code or token, the attacker can gain access to the user's account in their own browser." is a good reminder that you should keep your token lifetimes short. We recommend minutes; use a refresh token to renew the token.

Stealing codes does not work because PKCE, you need to steal at least the entire request and response, will be blocked if PKCE is using a cryptographic transform.

Protections from stealing tokens are confidential clients (requires a remote machine compromise) or proof of possession like MTLS or DPOP (requires private key exfiltration).

Generally the most paranoid access token lifetime recommended is ~10 minutes. There are token revocation methods that let you go longer, but revocation is an active process while token refreshes can just fail because of some change of application state.


> don't use the implicit grant, please. Use the authorization code grant with PKCE. Here's an example (from a doc I helped write, talking about how bad the implicit grant is: https://fusionauth.io/learn/expert-advice/oauth/modern-guide...

This is a bit of a strange security model. The implicit grant vulnerability mentioned is essentially a supply chain attack where an untrusted library is loaded. True, it can compromise the implicit grant token, but, in a mode where the implicit grant token is hidden, it's still talking as the website, and can do ~most~ anything, more or less that the token can do by making a request with the cookie, or whatever.


It's not just a supply-chain thing. There are a bunch of subtle interactions between the implicit flow and the rest of the web security model. For instance, because implicit flow releases working tokens (not grants, tokens) directly to content-controlled Javascript, it can be a way of transforming open redirects into account takeover.


And replacing it with auth code flow fixes that? I'll go along with auth code flow to fix Safari (now everyone) breaking cookies, but the security aspect of PKCE always smelled to me.


In the standard auth code flow, the authorizing application (I forget the OAuth terms for it, but "TweetLater" or whatever) shares a secret with Twitter, independent of the secret (your authentication credentials) you share with Twitter. The code you, a user, get --- that's fed through your browser --- is in a sense "locked" to "TweetLater". Part of the idea of the implicit flow is that it's for settings where you can't do that, or where it doesn't make sense (SPAs, for instance, where the secret the SPA would share with Twitter would be have to be embedded in the public Javascript code).

PKCE is just a weaker version of that protection.


Thanks for taking a stab at it. I'm well aware of how the auth code flow works - I have yet to see a serious discussion though on how PKCE fixes the issues inherent in the implicit flow. Everything I've seen is "implicit bad, PKCE adds security to other flows, therefore PKCE fixes implicit grant problems". Which is of course non sensical, but great for memetic transmission.


I guess I'm not clear on where you're not clear. PKCE adds a secret that the redeemer of the auth code returned from (say) Twitter needs to know to use the code. The big problem with implicit flow is the extent to which it exposes that code; PKCE makes it harder to use the code once exposed.


As you yourself said - a problem with implicit flows is abuse of open redirects. It's a rare open redirect that is vulnerable to implicit that's not also vulnerable to PKCE protected auth code flow. This pattern is generally true across the board for implicit grant vulnerabilities. I was hoping you knew of known value-add versus what's usually seen (when anything is examined at all) - contrived scenarios where the attacker can only abuse minor parts of an open redirect or has JS execution but not the ability to make off-box calls, etc. The subtle browser interactions, as you call out, are the problem, not the one versus two legged nature of running session existence into tokens. (Of course, in auth code flow you now have RTs, so now you have N+1 problems...)

It's more of a discussion for the standards groups I suppose.


Probably a silly question, but how exactly does the refresh token help here? If your app is keeping the tokens accessible from javascript, then an attacker who (through XSS for example) can steal the short-lived access token could also steal the long-lived refresh token an "trade it" for a new access token, no?


> If your app is keeping the tokens accessible from javascript...

Great question. I should have been clearer.

If you use the implicit grant, you can't use the refresh token (there are work arounds like the silent refresh, though). So it's not an applicable question.

With the application code grant, you have other options. Refresh tokens, like all tokens, should be treated with care and not exposed to javascript.

As a sibling comment noted, you can do that by storing it server side. Sure, someone could steal your session and try to access protected resources through the server side code, but that's harder to do than stealing an access token and being able to present a bearer token to any protected resource. (Or a refresh token, which can then be presented to an IdP and exchanged for an access token.)

One other option we recommend is sending down the access and refresh tokens as secure, httponly cookies. In that case, you are relying on the browser's cookie security to protect against XSS attacks. This, while not perfect, is pretty darn good, and if there's a widespread XSS cookie vulnerability:

1. Everyone with a browser client will probably have issues.

2. You could invalidate all the refresh tokens at an IdP. Pain in the but for users, but this is similar to what GitHub did earlier this month.


Refresh token rotation seems to be the standard mitigation (https://auth0.com/docs/tokens/refresh-tokens/refresh-token-r...)


Sure, that mitigates the risk when an attacker finds a refresh token later and it's no longer valid because of rotation. And it means that a stolen refresh token would probably be noticeable because the legitimate user wouldn't be able to use it anymore. But in many attack scenarios the attacker would get the refresh token immediately and the time until discovery would be enough for them to cause damage, right?


Generally there is a big overlap between attack scenarios that allow for token exfiltration and attack scenarios that allow for code injection.

Someone doesn't need to exfiltrate a token to make the client do their malicious requests right on the user's device. And certain external mitigations, like anomalous API usage detection, won't see odd time-of-travel restrictions based on the estimated geolocations of two different IP addresses or a change in the user agent behavior (different user-agent header, different networking stack behavior, etc).

The difference is that some people argue that one of them is in scope for security measures, while the other is out of scope.


Usually the refresh token would be kept server side only


OAuth 2.1 addresses some of the deficiencies in OAuth 2.0. In summary:

* PKCE is required for all OAuth clients using the authorization code flow

* Redirect URIs must be compared using exact string matching

* The Implicit grant (response_type=token) is omitted from this specification

* The Resource Owner Password Credentials grant is omitted from this specification

* Bearer token usage omits the use of bearer tokens in the query string of URIs

* Refresh tokens for public clients must either be sender-constrained or one-time use

See https://oauth.net/2.1/


Regarding the access token...

> the server does not have any secrets or passwords to compare with the submitted data, which means that it is implicitly trusted

The access token should be signed right? So it's not implicitly trusted. The server must of course validate the access token using the public key indicated in the token, and of course only accept tokens from trusted providers. Maybe I'm not reading this right.


That whole section is about the implicit grant anyway, which should be avoided.

As far as validating tokens, access tokens are 'opaque' in OAuth2; the token format is not defined by the spec. (In OIDC, they are JSON web tokens.) For OAuth, you can make a call to the introspect endpoint and the Authorization Server will tell you if it is a valid token.

In practice, OAuth servers often issue JWTs, and there's a draft spec out to make them interoperable: https://tools.ietf.org/html/draft-ietf-oauth-access-token-jw...

If you are a resource server consuming an access token and you know the token is a JWT, you should check the following at a minimum:

   * the token is signed according to the algo and the key that you expect
   * the token is not expired (the `exp` claim) and is currently valid (the `nbf` claim, if present)
   * the token is issued by who you expect (the `iss` claim) (which is the point you are making about 'trusted providers')
   * the token is issued for you (the `aud` claim)
Most libraries I have interacted with check the first two, but not the last two.


> the token is signed according to the algo and the key that you expect

Important point! Especially with JWT. It must be the algorithm you EXPECT, not the algorithm in the token. Someone flubbed up and made the "none" algorithm mandatory for spec-compliant implementations. Unfortunately, that can result in any JWT being treated as valid.

It's a chicken-and-egg problem that the designers missed: you can't trust the token until you've validated it, but you'd have to believe the value of the algo field before you've validated it in order to check the signature.

There are libraries that will see "none" in the algo field and treat that as "this token doesn't have a signature that needs to be validated, so it's good".


> Someone flubbed up and made the "none" algorithm mandatory for spec-compliant implementations.

Which spec are you referring to? 7519? Per section 6 ( https://tools.ietf.org/html/rfc7519#section-6 ) "none" is an optional feature of JWT.

I am not aware of any spec related to OAuth which requires a server to issue or accept a JWT with an algo of "none". Can you point one out to me?

From the AS side, our product won't even allow you to "sign" a JWT with a value of none. I'm not sure about other authorization servers.

And I always recommend that if a resource server ever sees an algorithm of "none" it should throw out the JWT full stop.

> It's a chicken-and-egg problem that the designers missed: you can't trust the token until you've validated it, but you'd have to believe the value of the algo field before you've validated it in order to check the signature.

What is the way around this? Looks like Paseto tokens (an alternative I've heard mentioned) works by not allowing unsigned tokens: https://github.com/paragonie/paseto

Is there another way to fix this?

> There are libraries that will see "none" in the algo field and treat that as "this token doesn't have a signature that needs to be validated, so it's good".

Spooky! Can you share these so I can stay far away from them? :)


> Which spec are you referring to?

OK, this one's on me. I can't find the reference, and I took an article at face value: https://auth0.com/blog/critical-vulnerabilities-in-json-web-...

There are libraries that allow Bad Things, though, and best practice now is just to ignore the `alg` field. As the github repo you linked says, "There have been ways to exploit JWT libraries by replacing RS256 with HS256 and using the known public key as the HMAC-SHA256 key, thereby allowing arbitrary token forgery."


Unencrypted, client legible access tokens is probably one of the worst ecosystem mistakes you can make. It lets devs (like the article author) read oauth as oauthN "because I can read the token". So now you have clients taking a silent dependency in your ecosystem on being able to predictably read the access token, even though they're not the audience. Suddenly, decisions like "change the format of the token for this resource" or "let's start encrypting" become ecosystem shattering projects.

Encrypt your access tokens by default if you want to guarantee clever clients don't try to take a dependency on them.


It is a conflation of the access token (message to the protected resources about the client) and the identity token (message to the client about the user).

OAuth doesn't define the concept of an identity token, hence it not being usable itself for authentication OpenID Connect extends OAuth with an identity token, which is of course readable by the client.

To be fair, Facebook Connect explicitly made this choice to combine the identity token into the access token - so their access token is necessarily client-readable and verifiable.


Yes, indeed :-)


> the token is issued for you (the `aud` claim)

My goodness I wish I was allowed to disclose a place where this was used for privilege escalation and not checked in any way.


I didn't know the token format wasn't specified, thanks for the clarification! I've only ever come across JWT:s.


>As a result, this behavior can lead to a serious vulnerability if the client application doesn't properly check that the access token matches the other data in the request.

Later they use this language the clarify the situation. I suppose the situation being described is when the token has no information that can track to a specific user id. I guess devs are just checking that a token decrypts and aren't verifying it matches the user id in the request?

Seems like basic security 101.


It can be authenticated as coming from a trusted party but cannot be authenticated against a user password because the system has no way of knowing any secrets provided by the user.


Well, that's kind of the point right? Keeping user secrets out of the trusting system.


You had one job...




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: