Thanks, Will, for sharing this! As I wrote in the GitHub issue, I was able to reproduce this attack using a VPN client (a desktop app with a loopback callback URI). It is interesting that this is a new option to get Browser Swapping with URL sharing. Therefore, these are the updated three options for attackers to achieve Browser Swapping with URL sharing:
1. Callback to client with the client’s expected response mode, where the user gets stuck in an error page containing the authorization code in the URL, because the state does not match. This affects web clients in general, as well as desktop apps using loopback or HTTPS callback URIs. According to RFC 6749, the client must fix this by redirecting to a dedicated error page whose URL does not include the authorization code. We already did a good job here, so this is up to the client developers. 2. Changing the response mode from query to fragment (or vice-versa), so that the client will not recognize the authorization code in the URL. If the response mode was changed to fragment, the browser even preserves the authorization code in the URL after a redirect to the dedicated error page (see 1). This affects JS-frontend and backend web clients in general, as well as desktop apps using loopback or HTTPS callback URIs. It can be fixed by the authorization server enforcing the response mode that the client expects. We should include in the standards that authorization servers MUST provide response-mode enforcement capabilities individually for each client, and RECOMMEND that client developers enforce the response mode implemented by the client. This eliminates this issue and 1. eliminates the entire URL sharing issue. 3. [New] Callback to a URL that is unreachable or failing on the legitimate user’s device. This affects all desktop apps using loopback callback URIs, as well as desktop and web clients using HTTPS callback URIs, which may not work, e.g., because they are not reachable from the user’s device (e.g., internal websites hidden behind a VPN) or fail (e.g., because of too long URLs or XSS protection). I agree with Will that the only way to fix this is to enforce response_mode=form_post, since it is the only response mode that does not include the authorization code in a sharable URL. We should RECOMMEND using response_mode=form_post for all clients using an HTTP backend (server-side or on the loopback interface) as the callback URI. This works for desktop clients with loopback callback URIs. It works for backend web clients as well; however, I have doubts that XSS protection mechanisms in POST bodies might still cause issues here, so we should mention that in the specs. For JS-frontend clients unreachable by legitimate users (e.g., behind a VPN) or with services that impose URL length restrictions or require XSS protection, I think we don’t have a solution yet, because clients cannot receive POST requests without involving a backend, so form_post won't work and query and fragment parameters will remain sharable in the URL bar of the browser that does not reach the website. It's also worth thinking about mobile apps here. If they use HTTPS redirect URIs, but the app is not installed, the callback URI will open in the browser instead of launching the app. Then we might run into the same issue mentioned in 3. As Will mentioned, the loopback option is out because of iOS restrictions. I’m not entirely sure what happens if we use a custom URL scheme, but I would not expect to end up with the browser showing the URL; however, I could imagine some operating systems that cannot map the URL scheme to an app, and they will end up making the entire URL copiable. @Aaron: Can we agree that response modes should be part of OAuth, at least to fix option 3? Greetings, Jonas > Am 18.12.2025 um 21:21 schrieb Will Bartlett <[email protected]>: > > Hi folks, > For those who don't know me - I work on Microsoft Entra's OAuth > implementation. I write this mail to address a few aspects: a) attacks in the > wild b) interactions with RFC 8252: OAuth 2.0 for Native Apps and c) > reliability of these mitigations that rely on the client. > > A. Regarding attacks in the wild - there is now a blog post which documents > this browser swapping attack against Entra using loopback URIs for native > clients. It even has a fancy name ("ConsentFix"). > Seehttps://pushsecurity.com/blog/consentfix for that blog post. > > B. Regarding RFC 8252. Many of the mitigations discussed in this thread are > not applicable to native clients that use patterns recommended in RFC 8252: > OAuth 2.0 for Native Apps. RFC 8252 recommends three patterns for native > clients: > 7.1 Private-Use URI Scheme Redirection > 7.2 Claimed "https" Scheme URI Redirection > 7.3 Loopback Interface Redirection > > Let's focus on 7.3 loopback. These clients register http://localhost > <http://localhost/> in the app registration and listen on a random port. In > the browser swapping attack, the targeted client is running on the attacker's > machine and not the victim's machine. When the authorization server redirects > back to localhost, nothing is listening on the victim's machine, and so the > browser returns a 404, leaving the authorization code in the URL bar. This > setup invalidates two mitigation options: > Authorization Code Invalidation (from slides - > https://datatracker.ietf.org/meeting/124/materials/slides-124-oauth-sessa-browser-swapping-01) > - this mitigation option suggests that when an application receives a code > that it cannot use, the client should send the code to some endpoint to > invalidate it. This mitigation option is inapplicable because the client is > not running on the victim's machine, and therefore does not receive the code, > and does not have the opportunity to send it to an endpoint to invalidate it. > Redirecting after receiving credentials. From the first reply on the thread > (Niel Maddon) - "in the original OAuth RFC 6749, which already says [...] to > redirect immediately after collecting the credentials." This mitigation > option is also inapplicable because the client is not running on the victim's > machine, and therefore does not have the opportunity to respond with a > redirect. > From what I can see, the only viable mitigation option for URL sharing > browser swapping for RFC 8252 7.3 loopback clients is to mandate > response_mode=form_post, because this is the only mitigation option that does > not rely on the client. > > There is also a bit of an interaction among these options. It's somewhat > common for application to use 7.1 private-use URI scheme redirection on > mobile platforms (Android and iOS) while using 7.3 loopback interface > redirection on desktop platforms (Windows and MacOS). In particular, iOS does > not permit listening on loopback, and Windows requires private-use URI > schemes to go through OpenWith / Default Programs - so other options are > chosen. However, 7.1 private-use URI scheme redirection only supports query > parameters. This combination is a bit of a roadblock for applications that > might provide a hypothetical "response_modes" client metadata - they must > enable the query response mode for mobile to support iOS and they must > disable the query response mode to secure loopback on desktop to support > Windows - contradictory requirements. There are various options here - such > as providing a response_modes metadata per redirect uri or per redirect uri > class (response_modes_native_loopback, response_modes_native_private_scheme, > response_modes_native_claimed_https, response_modes_confidential_clients) > instead of at the client level, or requiring applications to register > different clients for mobile and desktop platforms. > > C. Regarding the reliability of mitigations that rely on the client taking > action (authorization code invalidation and redirecting after receiving > credentials). I have doubts about these mitigations even for confidential > clients. Web applications are increasingly layered. For example, many > applications incorporate an XSS filter, which rejects payloads that seem > XSS-y (see OWASP discussion here: XSS Filter Evasion - OWASP Cheat Sheet > Series > <https://cheatsheetseries.owasp.org/cheatsheets/XSS_Filter_Evasion_Cheat_Sheet.html>). > For another example, many applications have proxies in front of them, > proxies which have limits in URL length. A clever attacker might try to > exploit this layering by carrying out a browser swapping attack with e.g. > state=<'javascript:'foo.exe'%/> - a payload that is designed to trigger the > XSS filter, or e.g. state=strrep('a',5*1000*1000) - a payload that is > designed to trigger the proxy's URL limit. Such attacks may evade mitigations > that rely on the client taking action by causing a failure in the client's > infrastructure prior to the client's application-level code running. > > My suggestion is to focus on mitigations that do not require client action, > specifically the response_mode=form_post mitigation. > > I have left some comments about loopback specifically on the PR mentioned. > > Thanks, > Will Bartlett > From: Primbs, Jonas <[email protected] > <mailto:[email protected]>> > Sent: Monday, November 3, 2025 8:09 PM > To: oauth <[email protected] <mailto:[email protected]>> > Subject: [EXTERNAL] [OAUTH-WG] Browser-Swapping > > You don't often get email from [email protected] > <mailto:[email protected]>. Learn why this is important > <https://aka.ms/LearnAboutSenderIdentification> > Hi all, > > according to Aaron’s recommendation, I have created a PR for OAuth 2.1: > https://github.com/oauth-wg/oauth-v2-1/pull/230 > > It references OpenID Connect’s response modes (fragment and form_post) as > solutions for Browser-Swapping attacks, which I have presented in today’s > OAuth WG meeting. > If you have missed my presentation, but are still interested, here are my > slides: > https://datatracker.ietf.org/meeting/124/materials/slides-124-oauth-sessa-browser-swapping-01 > > I’m interested in your feedback on this first draft, which currently covers > only recommendation #2 from my slides, because this is probably the least > controversial change. > If you are attending onsite, also feel free to speak to me in the hallway. My > company gave me enough of the „No, PKCE…“ t-shirts for the rest of the week, > so that it’s easier for you to find me. @Brian & Mike: I have learned from > the best ;-) > > Greetings, > Jonas > > > Jonas Primbs M.Sc. > University of Tübingen > Faculty of Science > Department of Computer Science > Sand 13, 72076 Tübingen, Germany > Tel.: (+49) 7071 / 29-70512 > Mail: [email protected] > Web: https://kn.inf.uni-tuebingen.de
smime.p7s
Description: S/MIME cryptographic signature
_______________________________________________ OAuth mailing list -- [email protected] To unsubscribe send an email to [email protected]
