OAuth 2.0 is the predominant authorization standard on the modern web. OpenID Connect (OIDC), a widespread extension of OAuth 2.0, is used for most single sign-on (SSO) systems today. Unfortunately, the OAuth and OpenID standards do not provide sufficient protection against so-called browser swapping attacks. Therefore, many OAuth 2.0 and OpenID Connect implementations are vulnerable to these attacks. This allows attackers to exploit existing protections against cross-site request forgery (CSRF) attacks to steal unused authorization codes. They can take over the user’s client session by letting the user log in via a typically harmless link.
OAuth 2.0 Authorization Code Flow
The OAuth 2.0 standard, as defined in RFC6749, describes multiple methods by which a user, called “resource owner”, can authorize an application, called “client”, after logging in to an identity provider, called “authorization server”. The most recently used method, which is also used by OpenID Connect, is the OAuth Authorization Code Flow, which is illustrated in Figure 1.
Figure 1: OAuth 2.0 Authorization Code Flow
- When a resource owner visits a client website that requires authorization, the client’s back end initiates an authorization code flow by generating an authorization request.
- The client’s back end then redirects the resource owner’s user agent (a web browser) to the authorization URL of the authorization server.
- The user agent follows this redirect to the authorization server.
- The authorization server will authenticate (authN) the resource owner if they do not have an active session yet. They may then authorize (authZ) the client’s requested permissions, such as reading their profile information, like their name or e-mail address.
- The authorization server then generates an authorization code and includes it in the URI as a query parameter, redirecting the user agent back to the client.
- The user agent follows the redirect URI back to the client.
- The client then exchanges the authorization code for an access token from the authorization server. The resource owner has successfully logged in.
Cross-site request forgery attack (CSRF)
The redirect URI browsed in step 6 contains an authorization code personalized for the resource owner who performed steps 1 through 5. Whoever browses this URI will be logged into the client with this resource owner’s account. This allows an attacker to perform steps 1 through 5, and launch a CSRF attack, as illustrated in Figure 2. Therefore, the attacker tricks the user into calling this redirect URI, which logs in the user with the attacker’s account.
Figure 2: CSRF attack on the OAuth 2.0 Authorization Code Flow
On Pinterest, this allowed attackers to link their social media accounts to other user’s Pinterest accounts as a backdoor link. Attackers might also use this method in online shops to steal payment information of users. Therefore, attackers log in the user with their account and wait until the user stores payment information on the next checkout.
CSRF prevention
The OAuth 2.0 standard recommends using the state parameter to prevent such attacks, as illustrated in Figure 3.
Figure 3: OAuth 2.0 Authorization Code Flow with CSRF protection
The client back end generates and stores this unguessable parameter for the resource owner’s session and adds it to the authorization request URL. The authorization server reflects this state parameter in the authorization response. Because attackers cannot guess the state parameter of the user’s session, the client detects such CSRF attacks and therefore rejects the authorization code. Consequently, the client will not send a token request to the authorization server, leaving the authorization code unused. The authorization code therefore remains valid.
Browser swapping attack
Unlike classic CSRF attack, in this case the attacker wants to log in with the user’s account. To do so, the attacker performs steps 1 and 2 in their own browser, as illustrated in Figure 4.
Figure 4: Browser swapping attack on user side
The attacker gives the user the authorization request URL (step 3), e.g., via e-mail, instant messaging, or via a manipulated website.
Then, the authorization server authenticates the user (step 4) and redirects them back to the client (steps 5 and 6).
If the user already has an active session with the authorization server, step 4 may be skipped without requiring further interaction from the user.
However, since the state of the user’s session does not match the state parameter defined by the attacker, the client detects a CSRF attack and denies the request.
Figure 5: Browser swapping attack on attacker side
Next, the attacker must access the authorization code query parameter from the authorization response, which will be described in the next section.
As illustrated in Figure 5, the attacker repeats the authorization response in their own session (step 6).
Since the state parameter of the attacker’s session matches the state parameter from the authorization response, the client resolves the authorization code.
Consequently, the attacker is logged into the client with the user’s account.
Accessing the authorization code
Because the authorization code is transferred in a GET request as a query parameter, it can be leaked in various ways. Over the last three months, SySS has discovered multiple methods of leakage depending on the client application.
1. URL sharing
Some clients respond to the authorization response (step 6) with an error, leaving the authorization code visible in the browser’s URL bar. Using social engineering, an attacker may ask users to browse the authorization request. After seeing the error message, users typically inform the attacker that the link does not work. Then, the attacker asks the users to share the link to the error page, which contains the authorization code.
2. System logs
Query parameters are often logged in various locations, as illustrated in Figure 6.
Figure 6: Attack surfaces for system logs
- Clients, e.g., web browsers, may leak the URL in the browser history or in debug logs which can be misused on shared workstations.
- Content delivery networks (CDNs) may log the authorization code from the
refererheader when the browser downloads static files like/favicon.ico. - HTTP proxies, and reverse proxies or load balancers typically log all URLs, including query parameters, or forward them to third-party extensions such as intrusion detection and prevention systems (IDPSs).
- Client-back-end servers typically log received query parameters, or forward them to third-party extensions such as web application firewalls (WAFs).
- Centralized monitoring systems collect all logs and enable low-privileged auditors to access authorization codes from high-privileged administrators, allowing for privilege escalation.
Mitigation strategies
In general, sharing secrets like authorization codes in query parameters is a bad idea.
Therefore, alternative response modes must be used, e.g., Form Post or Fragment.
The client indicates this response mode by adding the query parameter response_mode to the authorization request (step 2).
“Form Post” (response_mode=form_post) lets the authorization server respond with an HTML form.
Using JavaScript, this form submits itself to the client, containing the authorization code as a POST parameter.
Developers should prefer this mode for all client that can receive POST requests directly, i.e., back-end clients.
“Fragment” (response_mode=fragment) lets the authorization server respond with the authorization code as fragment parameter (#code=xyz instead of ?code=xyz).
Browsers do not send fragment parameters to the server, restricting the attack vector to the client itself.
Developers should prefer this mode for all other client applications, i.e., single page applications or mobile apps.
Since the attacker defines the authorization request URL, attackers could downgrade this response mode to query.
Therefore, client developers must ensure that the authorization server enforces the chosen response mode.
Unfortunately, some authorization servers like Keycloak do not provide the option to enforce a specific response mode.
In addition, client developers should invalidate any authorization code that they see, specifically if they detect a CSRF attack. So instead of ignoring the authorization response (step 6), the client must send any authorization code to the authorization server. If a CSRF attack is detected, this should invalidate the authorization code instead of issuing any token. Unfortunately, standard documents are unclear, when authorization server must invalidate authorization code. Therefore, the exact process how a client can invalidate an authorization code depends on the specific authorization server implementation. SySS discovered two methods, of which at least one works for most investigated authorization servers when sending a token request:
- Providing no or a wrong
redirect_uriparameter - Enforcing PKCE without providing the
code_challengeparameter
Conclusion and future steps
SySS discovered that the underestimated browser swapping attack threatens many implementations, and standards do not provide sufficient mitigation. At the week of publishing this document, SySS IT Security Consultant Jonas Primbs attends the IETF 124 meeting in Montreal. His mission is to discuss possible solutions with standard authors and specify a clear solution for the upcoming OAuth 2.1 standard.