April 9, 2026

Episode 169: Attacking OAuth 2.1

Episode 169: Attacking OAuth 2.1
Critical Thinking - Bug Bounty Podcast
Episode 169: Attacking OAuth 2.1
Apple Podcasts podcast player badge
Spotify podcast player badge
Castro podcast player badge
RSS Feed podcast player badge
YouTube podcast player badge
Apple Podcasts podcast player iconSpotify podcast player iconCastro podcast player iconRSS Feed podcast player iconYouTube podcast player icon

Episode 169: In this episode of Critical Thinking - Bug Bounty Podcast gr3pme goes over some of the changes from OAuth 2.0 vs 2.1 and how Hackers can capitalize.

Follow us on twitter at: https://x.com/ctbbpodcast

Got any ideas and suggestions? Feel free to send us any feedback here: info@criticalthinkingpodcast.io

Shoutout to YTCracker for the awesome intro music!

====== Links ======

Follow your hosts Rhynorater, rez0 and gr3pme on X:

https://x.com/Rhynorater

https://x.com/rez0__

https://x.com/gr3pme

Critical Research Lab:

https://lab.ctbb.show/

====== Ways to Support CTBBPodcast ======

Hop on the CTBB Discord at https://ctbb.show/discord!

We also do Discord subs at $25, $10, and $5 - premium subscribers get access to private masterclasses, exploits, tools, scripts, un-redacted bug reports, etc.

You can also find some hacker swag at https://ctbb.show/merch!

====== This Week in Bug Bounty ======

Intigriti is providing free Burp Pro for Hackers!

https://www.intigriti.com/blog/news/intigriti-collaborates-with-portswigger-to-support-ethical-hacking-excellence

====== Resources ======

Django-allauth Account Takeover (ZeroPath Audit)

https://zeropath.com/blog/django-allauth-account-takeover-vulnerabilities

CVE-2025-4144: Cloudflare Workers PKCE Bypass

https://github.com/cloudflare/workers-oauth-provider/security/advisories/GHSA-qgp8-v765-qxx9

CVE-2025-54576: OAuth2-Proxy Auth Bypass

https://zeropath.com/blog/cve-2025-54576-oauth2-proxy-auth-bypass

====== Timestamps ======

(00:00:00) Introduction

(00:02:16) OAuth 2.0 Standards

(00:12:08) Agent to Agent Communication

(00:17:19) CVE Case studies

[00:00:01.30] - Brandyn Murtagh
If this becomes a standard, which I think it will be and slowly is, it actually opens up some more attack surface because we have a little bit more to play with. The best part of hacking, when you can just, you know, critical think, right? Yeah, dude.

[00:00:33.25] - Justin Gardner
All right, y'all, we've talked about ThreatLocker  Ring fencing a lot. We know how it allows you to set ACLs and policies for exactly what an application is allowed to do in your network. But today I'm going to tell you how it does that. Okay. 3 technologies: mini filter drivers, window filtering platform, and EX version. Kernel notification routines. MiniFilter drivers are essentially a hook or callback for I/O requests, okay? So when you're trying to write or read from a file, you can create a hook with those and approve or deny based off of the ThreatLocker ACL. Windows Filtering Platform, similar situation, but for connect-bind requests, right? You can say, okay, hey, is this process allowed to talk to port 443 over there on the internet? Yes or no? Approve or deny. Lastly, you've got ExVersion kernel notification routines. The ExVersion is important because it allows you to approve or deny versus just getting a notification from the kernel. And this is specific for hooking various native API calls like NtCreateUserProcess, which is what you would use to, like, spin up PowerShell.exe or something like that. Okay? So those three are very useful to Threat Lockers, ring-fencing core technology. Hope you enjoyed learning how this is implemented. Check out Threat Locker if you think your work could benefit from something like that. All right, let's go back to the show.

[00:01:49.60] - Brandyn Murtagh
Hey, what's up guys, it's Brandon, and this week in bug bounty is short, but it is very good news for some of you hackers out there, and it's from Intigriti. Now, Intigriti is collaborating with PortSwigger, and what this means is if you get a certain amount of reputation points per quarter, you'll be getting a free 6-month Burp license. Now, In order to be valid for this, you need to achieve 400 or more valid reputation in a single quarter, and then you get a free 6-month Burp license. Now, I'm not too sure how long it takes for the license to come through, but you will be eligible for a license. So if you haven't got a paid-for proxy yet, if Burp Suite is your go-to, or if you want a community edition, this one might be for you, and it's a very good way to get a paid-for license. So check that out if you are in need of a Burp Suite license. As I said, 400 reputation. And looking at the score breakdown on Integriti, um, exceptional's 50 points, critical's 40 points, high's 25, medium 20, low 5 points. So just to give you a bit of an idea of what you'll have to be hunting for in terms of quantity. So if you need a license, definitely check that out. But yeah, that's it for this week, guys. Have fun. Hey guys, it's me, Brandon Gretme back with a solo episode today. It's been a while since I've last been on, um, I can attribute that to LHE season. It's been very busy. I've had 3 back-to-back events now for what feels like 2 years instead of 2 months, so it's been very busy. And now Rezo and, and Ryan Rater out on their LHE, so it's just going to be me today. Now, this episode is being driven from a part of the scope that I focused on in one of these live events, and I realized when I started digging into RFCs and looking at some of this functionality, um, how impactful it could be on the rest of our scope and how we look at things going forward in the OAuth spec and the OAuth implementation. Now there's a few upcoming changes happening, um, with regards to the OAuth 2.1 standard, and I'm just going to share my screen real quick for people watching. Now the OAuth 2.1 standard, some providers have already started implementing this for agent-to-agent stuff, but we'll get on to that shortly. But the OAuth 2.1 standard states that PKC is mandatory. The implicit flow has been removed. The client credentials— sorry, not client credentials— the password credentials flow is getting removed. Redirect URI matching has to be exact string only instead of any wildcards or some of the loose path matching that it does now in some frameworks. Bearer tokens are no longer allowed to be in URIs. Either. So that massively changes the attack surface from what we're used to. So just to summarize, servers must reject requests that miss the code challenge parameter. Implicit and parser credentials flows are going to die out. Redirect URIs require exact string matching. No more access tokens and URIs. And This is quite interesting because it brings quite a significant change to what we're used to. So the new proposed changes, which some frameworks are already implementing, is that the authorization header is preferred for transmitting the token instead of the URI. And also, if this header can't be utilized for whatever reason, it can be transmitted as form-encoded within a request body. So So what led me to this is on one of the targets— I can't say, um, unfortunately— but there was some interesting functionality around the mCP and agent-to-agent authentication and how that's handled as well. Now this actually brings an entirely new threat model to OAuth frameworks as a whole if they start implementing and becoming 2.1 spec compliant, and even if their ecosystem starts to change as well. And that is because of the MCP authorization spec. Now I'm just going to reshare my screen here. Now the MCP authorization spec, I've got a diagram up for people that aren't listening, builds on top of OAuth 2.1 that then uses and builds on top of RFC 8414 and RFC 7591, which is server data Server Metadata Discovery and Dynamic Client Registration, and that then builds the MCP authorization spec, um, as a whole. So the traditional flow that we're used to with the Dynamic Client Registration flow is we send the HTTP POST request to the register endpoint containing redirect URI, grant type, logo, um, client name, and then the authorization server returns client ID, client secret, and issue that. Now we're very much used to that traditional threat model when you, um, introduce the logo URIs for SSRF and things like that. Um, but now with the new introduced client identity metadata document, that changes a little bit. So the flow here is the agent sends a request to the authorization server containing a client manifest URI, a scope, and a response type. That authorization server takes that and stores it in the metadata verification service, or verifies it there as well, and is parsing and validating the redirect URI, the JWKS URI, logo URI, and so on. And then that metadata verification service sends that back to the authorization server, um, yes, no, whatever, provides a consent and login screen, and then passes that auth code back to the, to the AI agent. Now this is interesting because the threat model changes here a little bit because, I mean, the authorization server and the metadata verification service can be on the same host, but we're now passing a client manifest URI to both of these services where it has to be validated. So the most logical thing to go after when we're looking at this new implementation is SSRF. Now SSRF on the metadata verification service is very much still going to be present because there's either going to be the authorization server itself handles that, or the external identity verification service handles that. And by the very nature of it, they have to request the client manifest URI and validate that themselves. So when we start looking at how that validation occurs, which is some of the things I was doing on one of these targets in the live event, is trying to understand, okay, when this flow happens, first of all, understand what is the user, user agent that's making this flow. Second of all, off the back of that, does the service that's handling the redirect and actually handling the request of that document, does it handle different response codes? How does it handle those different response codes? Does it handle different, uh, 3XX response codes differently? Are there any specific quirks on that specific user agent, if we have access to that? A long shot, but if— does it handle JavaScript processing as well? If it's maybe a headless browser that's doing that, um, and going down those very traditional SSRF attack vectors. And it's quite nice because there's two different spots where this might potentially occur as well with this new client identity metadata document registration, because you have the first request to the client manifest, and then inside the client manifest as well, you can nest a whole range of URIs as well, which may may not be supported depending on the framework. So there's two different places where this can really come up and, uh, and, and increase that attack surface. Now the other thing as well with this is because the client must fetch the client JSON document, um, and must enforce that all redirect URIs in the document belong to the same domain, we start to get into the standard— how is the domain validation made. So say for example, if the service isn't validating that the domain you pass in as a redirect URI doesn't match your actual own domain, you might be able to set a redirect, for example, to attacker.com or victim.com/callback. It might accept that weak domain check, and you might be able to use that as a primitive to leak to an external domain as well. And the other thing that I just wanted to touch upon as well that I did touch upon is the URIs in the CI/MD flow, which you can pass in inside the client.json. I just wanted to, to clarify this. It's a logo URI and the JWKS URI. So test case, in your client.json, you nest the logo URI as the, um, a target, say for example internal, whatever it might be, metadata endpoint or internal host. You set that in if the tool server fetches the logo URI blindly and maybe displays the logo in the UI or gives an error on that, then you've got SSRF there as well. And the other thing as well, which I was thinking about when I started to threat model this service, is since the CIMD is a static document, the, um, authorization server might start to cache some of these requests. So for example, if it's validating the client.json and it caches the results, that gives a whole leeway there for your traditional cache deception and, and all those types of attack vectors as well. So overall, I'm just going to share the client identity metadata document again. So overall, even though the dynamic registration is going to be slowly phased out over time, if this becomes a standard, which I think it will be and slowly is, it actually opens up some more attack surface because we have a little bit more to play with in this flow as well, and there's more things to verify. So it isn't all bad. So that's the second change I thought that was quite interesting when I started to deep dive and look at some of these documents off the back of the target. Okay, so On to the next one, and that is the token exchange RFC 8693. So right now, agent authentication is in a, is in a good spot, but agent authorization, not so much. I'm just going to share my screen now. Right now, there's a lot of token pass-throughs everywhere, which is if you hack AI or if you're looking at these multi-agent workflows on the on the blue team side, you'll know that this is a problem where token passthrough right now— so the user token has all the scopes which then get passed to Agent A. Agent A needs to spin up Agent B, um, and Agent C in an agentic workflow and delegate all these different tasks over. And right now all these agents inherit the same token and the same scope. Now the token exchange RFC 8693 gives a mean for agent-to-agent communication to reduce the scope on each hop. So say, for example, your user token, the scope is full as you'd expect. You give that to Agent A, and the scope you only want to give Agent A, because it handles a very specific task, is just calendar read, for example. Now, when Agent A invokes, um, Agent B, for example, it can do that on the next hop and just give it a reduced scope for that hop. So you get from this situation where you have a full scopes token being passed from every single agent, and it just gets passed downstream. If you look at any of this traffic, it will simply just have— you have an authorization header with that token, and each agent just appends it on and tacks it on sends it to the, to the next call. Um, now this addresses that, where you can reduce the scope for each hop in that agentic workflow on agent-to-agent communications. So that's pretty nice. And to be honest, if you're in any AI circle, that's been spoken about quite a while now, where authorization is just a mess. If you look at any targets I think one of the most common bugs is around confused deputy attacks and just agentic services or chatbots, for example, just having almost god mode to everything. So this, although slightly different, aims to address some of that overprivileged, uh, overprivileged scope creep that keeps cropping up all over the show. So That would be quite nice when it's implemented. Well, not nice for us, but something to look out for at least. Okay, some other things before moving on. I just wanted to mention on the token exchange as well is that this is already cropping up in a few targets. The one that I'm aware of is Auth0. The token vault utilizes this architecture for enabling the AI agent integrations. And the other thing as well, um, token passthrough, uh, when you directly forward user credentials, is a primary driver for confused deputy exploits. Now, I know in one of the live events that people were just in, in Japan, there were a lot of these confused deputy-based attack vectors happening on this target. So definitely something to keep an eye out for. Okay, so in OAuth 2.0, PKCE is now required by default instead of being optional. And PKCE, Proof Key for Code Exchange, is used to prevent authorization code interception, essentially. And you would have probably seen it on requests through the code challenge parameter. Now It's interesting because in a normal PKCE flow, the client will send its code challenge, the server stores that code challenge, client then sends a code verifier, and then that's then verified. And if they match, you get the token issued. With a downgrade attack, um, the code challenge is stripped from the request or set to a default value. If the server accepts the request without it, that then facilitates the authorization codes to be stolen, then exchanged without no verify needed. So the main vector here, when this is implemented by default on frameworks that are 2.1 compliant or are migrating to be 2.1 compliant, is looking around the PKCE downgrade attacks. Now I do have a couple of CVEs that I found off the back of this research as well, which, uh, highlight this quite nicely. One of the big ones that I saw, which went under my radar initially, was by ZeroPath. And essentially it was in Django OAuth, which is quite a big library. And the implementation flaw was in the fact that it utilized the preferred username from the OAuth response as the account UID. When you are looking at these frameworks, it's important to understand what claims should be writable and what claims should be used as your source of truth, because there are claims that are mutable or writable by users themselves. And in this case, preferred username was actually, actually a mutable claim on certain providers. So what this allows to do is an attacker, for example, can set their preferred username themselves. They just set that to admin or victim.com, and they can get account takeover from that. I'm just going to show my screen on this research because it does deserve a shout out. Now, as I said, by ZeroPath, and it was 7 vulnerabilities in Django, all of which let them achieve account impersonation. Now, it is a pretty big library, as I said, with was at 2 million monthly downloads, and 4 major vulnerabilities were identified in the library with the different providers that it integrates with. Is that Okta identifiers were mutable, NetIQ identifiers were also mutable, tokens for deactivated users could be refreshed indefinitely. Now, in these live events The reason why I wanted to share this research is because these are the exact things I was looking for in some of these implementations as well. Um, and that is, if a user gets deactivated or if the state of that user account gets changed in some way to a state where the user shouldn't be able to access something, how is that— how is that user's token handled? And how is the user's refresh token handled as well. Um, can you just use the refresh token to still get the same claims and same attributes as it was when it was first issued, or is the refresh token also updated on the server side as well? Now the biggest vulnerabilities from this write-up are obviously the mutable attributes being used for certain providers. So as it says here, uh, the function uses the OAuth response field preferred username as the account UID. Many OAuth and OIDC providers expose a stable subject claim and an optimal user-facing field like preferred username. Preferred username is often mutable and not globally unique. Now that's important here because when an application or a library is identifying its users, um, for a claim, that claim has to be immutable because otherwise a user can just create their own claim or overwrite an existing claim and use it as a source of truth. Now this is actually also can be quite an interesting primitive for privilege escalation bugs. If you see a permission set or something else as a claim which, one, you can overwrite, and two, is being used as a source of truth for something in the application, definitely try and overwrite that because it can lead to some really interesting bugs. So just keep track of that as well. I think this, uh, blog writer was a RuneScape player as well, if you get the Zezima reference, however you say it. Um, but yeah, quite a good blog post. Um, it contains code snippets as well, so definitely go and check that one out by ZeroAuth. But those are the two points I wanted to mention. Now the other one I wanted to mention touches upon the point that we just talked about around the new RFCs and where some of this ambiguity is going to start coming in on implementation flaws. And that one is a PKCE bypass on Cloudflare workers. Now this one took a little bit of digging in. I've got the GitHub advisory shared on screen right now, but essentially the bug was in the handle token request. It accepted a code verifier even if the initial authorization request omitted the code challenge. So by stripping code challenge, you could essentially exploit the lack of enforcement and steal the auth code and exchange it without a valid verifier. Now, this one affected all Cloudflare workers as well. And you can see here that the MCP specification requires OAuth 2.0, stated in that advisory as well. So yeah, quite an interesting one off the back of some of the new implementation. Okay, so the next one is again by ZeroPath, and it is— let me share my screen— CVE-2025-5476, and it is an authentication bypass on OAuth2 Proxy, which is quite a big and popular gateway used for Kubernetes and cloud environments. And essentially, there's a feature in this framework that allows you to specify regex patterns for paths that should bypass authentication completely. So say if you have a specific path that you needed to be publicly accessible and you didn't want it wrapped or sat behind the proxy, you would just go, okay, here's a regex for that path, and how about it, and it'll be publicly accessible. Now, there's an implementation flaw in the fact that the regex actually matched against the entire request URI. Now that's interesting because if you match against the entire request URI, you have query parameters in there as well. And that meant that you could essentially match a request by doing something like, uh, /endpoint/endpoints/endpoint-100-access, um, question mark parameter equals, and then the regex which is used to, to skip that path. And because the parameter is also matched from that regex, it would lead to complete auth bypass for those specific endpoints. And what that would allow an attacker to do is actually get authentication being skipped for requests and endpoints that should have been protected, simply by matching that bypass regex in the— as a request parameter in the URI instead of actually being the URI itself. So quite, uh, quite an interesting CVE. Um, but yeah, this was by ZeroPoint, CVE-2025-54576 on OAuth2 Proxy. Now the last one that did make me laugh, which I'll share shortly, um, is from Pentester Labs, CVE from this year, quite fresh. And I didn't know that Harbour existed as a language. I didn't even know what Harbour was until I found this out, but still the bug's pretty funny. Um, anyway, the TL;DR here is that the classic None algorithm is supported, um, and in this one it was actually any unknown algorithm, uh, gets that bypass. So quite funny is you can set the algorithm to Badana or ZZZ or whatever it might be, um, it gets passed to the framework or library, or you pass in None and, uh, it just basically fails open and bypasses signature verification altogether. So that was CVE-2026-23993 by, uh, Pentester Labs. So check that one out as well. Okay, so just to wrap up for everyone, a bit of a shorter episode today, but just cover some really cool changes and interesting things that's going to start creeping in to things we're looking at. So The core OAuth 2.1 protocol PKCE is mandatory. Servers must reject requests that don't contain the code challenge. Implicit flow and password credentials are dead, they're going to be phased out. Redirect URIs now require exact string matching instead of wildcard matching that sometimes we're used to. Bearer tokens are prohibited in URIs. PKCE downgrade attacks are going to be quite fruitful. And some other things as well around where some of these bugs might come up is look for the transition gaps when frameworks are going from 2.0 to 2.1 and trying to be cross-compatible and make sure all their clients, um, are still in a working state. Is that there's going to be a lot of compatibility shims where a lot of bugs show up in. So if you see a target claiming OAuth 2.1 compliance, check out the downgrade attacks, for example. Um, now the second takeaway is the CIMD SSRF for the new way for the client metadata to register. Now that's an SSRF gold mine, so if you see that implemented or see support for it, definitely check it out. And pass in that metadata document and nest that with your own redirect URIs or targets, whatever you want to do as well. Now the third point is authentication is in a fairly strong spot, but delegation and authorization is still in a pretty bad spot, and there's a lot of bugs there. So look for token passthrough if an agent is calling at all, um, try and disclose how, how it calls that tool. Look at the traffic if you can, understand the request structure, and see if the tool includes a full privilege token rather than a small scoped token just for that specific action. So that's the takeaways for this week, guys. I think OAuth 2.1 is going to be quite interesting when we start seeing a lot of the frameworks and libraries that we interact with start implementing it and trying to get a lot of these changes cross-compatible, um, and ensuring that clients are supported. But that is it for this week. I think me and Justin will be covering some of the fun we had in the recent event in an upcoming episode, so look out for that. But yeah, that's it for this week, guys. Peace.

[00:29:51.72] - Justin Gardner
And that's a wrap on this episode of Critical Thinking. Thanks so much for watching to the end. Y'all. If you want more critical thinking content, uh, or if you want to support the show, head over to ctbb.show/discord. You can hop in the community. There's lots of great high-level hacking discussion happening there on top of masterclasses, hackalongs, exclusive content, and a full-time hunters guild if you're a full-time hunter. It's a great time, trust me. All right, I'll see you there.