尽管这几天有越来越多的人坐下来看文章(或许现在他们都把它留给他们的agents XD),总之,这是我为2026年柏林Pwn2Own建造的——一个完全手工制造,逻辑上只使用Microsoft Edge Full Chain,没有记忆腐败,根本没有AI!
确认 DEVCORE研究团队的橙色柴(@orange 8361)(@d3vc0r3)将4个逻辑bug连锁起来,在Microsoft边缘实现沙盒逃生,收入17.5万美元和17.5个普恩积分大师。 全赢!#Pwn2Own#P2OBerlin https://t.co/v 贾普GG4cD.
2026年5月14日
全链由五个bug和两个Windows特征组成。 我也分享了今年HITCON和Hixacon的捕虫和连锁过程,你可以检查幻灯片在这里!
而且,也许每个人都认为LLMs在AI时代已经可以翻遍一切,所以完全靠手制成的东西不知何故感觉更加珍贵。 这或许也是为什么这一连锁店被提名参加今年的普尼奖,并赢得了Epic成就奖。
让我们来导航吧!
那么,我们从哪开始一个只有逻辑的全链?既然是“浏览器 ”,让我们先看看“浏览”(或Chrimium所称的“导航”)如何运作。 我将不谈太多细节
我仍建议读Chrome大学的「航海生活」
被遗忘的导航喉咙
在我走过铬航行流时,我注意到了“Navigation Throttles”这个名字。 他们生活在浏览器进程,并拦截浏览器或伦德尔启动的所有导航请求。 也许事后很容易说,在过去五年中,CVES Google Chrome已经固定下来,与之相关的只有不到20个。 我想这也是个角落很多人不会注意。
总之,我开始挖掘导航喉咙检查Google Chrome和Microsoft边缘是如何执行的在通过代码时,我发现Edge有一个节流阀叫做EdgeCA GuideanceNavigation 为其企业配音 Entra ID功能(这里的CA代表有条件访问). 这个节流阀的目的是当Edge发现用户在Microsoft网站上与一个工作账户签约时,引导用户使用正确的用户配置。 要发射这个油门,你必须从一个特定的 Microsoft 拥有的源头开始导航,如登录。 live.com:
一旦节流阀被发射,Edge会比较当前用户配置文件的电子邮件与?email QQemail}. 如果它们不匹配,Edge 就会出现引导您切换账户的对话框。
但是如果它们真的匹配,Edge 将会抓住您原先要从 switch profile url} 前往的 URL,发布将 Chromium 的内部导航 API 进行新的导航:
因此,基本上,当目标URL匹配特定内部格式时,节流器取消原始导航,然后发布通过攻击者控制的查询字符串,如果满足了条件,则使用新的URL. 由于这种新的导航是由浏览器进程启动的,理论上我们可以做一些我们以前无法做的事情,比如跳跃到通常被禁的网页,如边缘/设置或devtools://...,甚至像javascript:alert(起源)这样的伪策略。
虽然JavaScript伪计划听起来像一个强大的原始,但你仍然需要在登录.live.com上获得执行背景,以便首先点燃节流阀。 因此,能够跳到javascript:并不真正赋予你任何额外的能力。 充其量就像一个自我XSS,或是将开放方向转换为XSS的方法。 无论哪种方式,您仍然只执行 JavaScript 在同一登录。 live. com 上下文中。
老实说,对我来说,它只是一个更强大的OpenRediction,可以跳到几个更多的浏览器-Level计划,仍然需要与其他bug连锁。 所以虫子刚在我的指出坐了几个星期。..
无源导航:一个新的 UXSS 初始
转折点是有一天我翻阅Chromium建筑开始思考:节流阀在浏览器过程中处理,因此新的导航也从浏览器中启动。 然而,我们发射的节流阀的页面,登录.live.com,住在Renderer中,那么,现在的TAB是指哪一个标签?
为了发现这一点,我做了一个快速测试,并在最初的触发器上增加了一个延迟:
在延迟期间,我们手动将边缘切换到一个新的标签,看看会发生什么,然后。..
轰!google. 为了充分理解UXSS的根源,我们挖掘了Chromium的源代码。 我们发现,如果调用Navigate( ) 不指定源内容( 绑定的 Render 源), Chromium 会自动使用当前活动标签作为 Render 源。
路径:铬/铬/浏览器/ui/浏览器导航器.cc
虽然代码路径需要参数。 配置为 CurRENT TAB 和参数。 来源内容是 NULL,幸运的是,这些是默认的,所以即使是 Chromium 本身的示例代码也会错误。 我想这应该算作一个新的UXSS攻击表面!
0点挑战
我们刚刚得到的UXSS原始听起来很棒,但射击仍然伴随着一些实际挑战。 在这里,我们列出四个我们在武装UXSS时面对,以及我们如何用越来越多的bug破解它们。
1. 必须从可信来源点火
正如我们先前提到的那样,第一个挑战:必须从某些Microsoft地点发射这种UXSS。 虽然我们依然可以在那里找到一个XSS来完成这个任务,相反,我们决定先看看原产地检查中是否有低挂水果。
我们发现EdgeCA GuideanceNavigation Throttle使用GetLastComcentURL()来获取发射导航的源头,然后检查。 乍一看,这似乎是进行验证的正确方法。 但再看一眼,你会注意到Microsoft似乎已经忘记导航不仅可以来自目前的WebContents,还可以来自另一个窗口。 所以简单的窗口。 open(...) 足以绕过它!
你可以在任何网站上运行这个来发射油门
2. 弹出阻塞器
目前,我们可以通过跨窗口导航绕过原产地检查。 然而,现代浏览器不会让网页打开如今容易的新windows,因此出现了另一个新问题:弹出屏蔽器默认会屏蔽窗口。
为了破解这个我们不停地扭转Microsoft边缘我们发现Microsoft,可能是内部使用,做了自己的硬码白名单弹出。.. 因此,像以下这样的弹出通常会像预期的那样被阻断:
但如果你把URL换成白名单的。.. 窗口就出现了然后记得把它带回原址
谢谢,Microsoft!
3. 需要签署简介
现在我们可以开火了下一步是通过有条件的访问检查代码首先要求 Microsoft Edge 已经有一个签名的用户配置文件,然后检查是否与配置文件的电子邮件相匹配。 在到达无源浏览器启动的导航系统最终触发我们的UXSS原始系统之前,必须满足这两个条件。
我们仍需要想办法让攻击成功,在探索Microsoft边缘时,我们发现了一个叫做“OneAuth”的大特点。 该功能允许任何 Edge WebView 应用程序在访问 Microsoft 拥有的站点时自动与您当前的MSA 证书签名。 结果发现,从25H2开始,Windows OOBE需要您用一个MSA(除非您使用本地账户的操作)签名。 因此,即使你从未在浏览器中创建过用户配置文件,仅仅访问www.microsoft.com就足以让边缘自动创建一个并签名。
再次感谢,Microsoft!
4. 需要事先了解登录电子邮件
最后一项挑战是让受害者提前收到电子邮件。 当然,我们可以尝试一些社会工程,或者继续扭转Microsoft边缘找到另一个漏洞泄露给我们。 但是,由于自动登录已经设置了几张Microsoft饼干,我们认为WebHacking是偷取电子邮件的更方便的方法。 于是我们启动了Burp套房并开始寻找网络弱点。
我们花了几个小时,在Microsoft网站发现了几个XSSbug. 我注意到嵌入在edge://new-tab-page-online的资产。 更有意思的是,当我检查了响应标题时,该网站似乎有CORS的错配置。.....
因此,如果包含一个简单的获取(...),您可以轻松地从任何网站获取 Microsoft 边用人的电子邮件:
一起来!
目前为止
- 缺失跨窗口打开器检查导致的可信来源绕行
- 硬码白名单引起的弹出阻断器绕行
- 自动在其中签名的 OneAuth 特性
- CORS的错误配置让我们窃取用户的电子邮件
现在我们终于可以一击就能触发UXSS了!更妙的是,由于这个UXSS是从浏览器进程(HandleRender DebugurL in Mojo,如果你有兴趣的话)启动的,它可以完全忽略任何CSP. 这意味着我们可以在google.com,facebook.com,icloud.com上运行JavaScript,或者我们想要的任何网站!
突破浏览器 Sandbox
我们有一个通用XSS。 这是否意味着我们可以做我们想要的?如果说网络安全,但是,我们的目标是获得ACE(任意代码执行),所以单靠UXSS远远不够。 我们还需要找到一种方法,从浏览器Sandbox中突破来将这个UXSS变成代码执行。
我想到的第一个也是最直接的点子是:既然我们可以在任何网站上运行JavaScript,或者更确切地说,任何来源,为什么不仅仅利用特权的WebUI网页,比如边缘/设置或边缘/下沉负载来获得ACE?
这种天真的方法当然已经被铬开发者缓解了。 在Render的Mojo处理器中,有一个额外的检查:如果来源属于以下特权的WebUI之一,Render只是忽略了JavaScript URL.
路径:铬/第三方/blink/nderer/core/frame/local frame.cc
路径:铬/第三方/blink/render/platform/webround/scheme registry.cc
基本上,Chromium已经把每个特权的WebUI都放在了不为人所知的 javascript urls schemes黑名单上。 那时,我仍然被困住了很久。
通过immersive Reader 任意文件写入
然而,当我们继续探索Microsoft边缘时,我们发现Edge有它自己的读取模式,叫做immersive Reader. 与Chrome的内置阅读模式不同,Immersive Reader似乎早在Chrimium时就已经防御了,上面有Microsoft建筑。 因此,Edge不使用旧的铬-distiller://或黑名单的铬-un trusted://,而是使用一种读作p/的习惯。 Microsoft似乎也忘记加入黑名单
从这个特权WebUI开始,我们可以通过JavaScript访问很多本地的C++对象。 其中一个叫做chrome.edgeFeedback Private.zipDiagnosticLogFiles()看起来特别有趣。
本文由 AI图鉴自动抓取并翻译,版权归原作者所有;如需查看原文请点下方链接。
I think I should still dump some of my work once in a while, even though it feels like fewer and fewer people actually sit down and read articles these days (or maybe now they all leave it to their agents XD). Anyway, this is what I built for Pwn2Own Berlin 2026 — a completely handmade, logic-only Microsoft Edge Full Chain, with no memory corruption, and no AI at all! 🔥
Confirmed! Orange Tsai (@orange_8361) of DEVCORE Research Team (@d3vc0r3) chained 4 logic bugs to achieve a sandbox escape on Microsoft Edge, earning $175,000 and 17.5 Master of Pwn points. Full win! #Pwn2Own #P2OBerlin https://t.co/vJaIpGG4cD
— TrendAI Zero Day Initiative (@thezdi) May 14, 2026
The whole chain is made up of five bugs and two Windows features. I have also shared the whole bug-hunting and chaining process at this year’s HITCON and Hexacon, plus some fun stories along the way. You can check the slides here!
Also, maybe everyone thinks LLMs can already pwn everything in the AI era, so something made entirely by hand somehow feels a bit more precious. That might also be why this chain was nominated for this year’s Pwnie Awards and won the Epic Achievement award. 🎉
Let’s Navigate!
So where do we start with a logic-only full chain? Well, since it’s a “browser,” let’s start by looking at how “browsing” (or what Chromium calls “navigation”) works. I won’t go into too much detail in this post, because I think in this era, you can just ask your agents to fill in the details :)
But I’d still recommend reading “Life of a Navigation“ from Chrome University if you want to get the full picture.
The Forgotten Navigation Throttles
While I was walking through the Chromium Navigation flow, something called “Navigation Throttles” caught my attention. They live in the Browser Process, and intercept all navigation requests initiated by either the Browser or the Renderer. Maybe it’s easy to say this in hindsight, but out of the 3,000+ CVEs Google Chrome has fixed over the past five years, there are only fewer than 20 related to them. So I guess this is also a corner not too many people pay attention to.
Anyway, I started digging into the Navigation Throttles, and checked how Google Chrome and Microsoft Edge implemented them. And while going through the code, I found that Edge has a throttle called EdgeCAGuidanceNavigationThrottle for its enterprise Entra ID feature (the CA here stands for Conditional Access). The purpose of this throttle is to guide users to the right User Profile when Edge detects them signing in with a work account on a Microsoft website. And to fire this throttle, you have to start a navigation from a specific Microsoft-owned origin, such as login.live.com:
And once the throttle is fired, Edge would compare the email of the current User Profile with ?email={email}. If they don’t match, Edge pops up a dialog guiding you to switch accounts.
But if they do match, Edge would then grab the URL you were originally heading to from switch_profile={url}, and launch a new navigation with Chromium’s internal Navigation API:
So basically, the throttle cancels the original navigation when the target URL matches a specific internal format, and then launches a new one with the URL from the attacker-controlled Query String, if the conditions are met. Since this new navigation is initiated by the Browser Process, in theory we can do a few things we couldn’t do before, such as jumping to normally prohibited pages like edge://settings or devtools://..., or even pseudo-schemes like javascript:alert(origin).
Though the JavaScript pseudo-scheme sounds like a powerful primitive, you still need to obtain the Execution Context on login.live.com to fire the throttle in the first place. So being able to jump to javascript: doesn’t really give you any extra capability. At most, it’s just like a Self-XSS, or a way to turn an Open Redirect there into XSS. Either way, you’re still only executing JavaScript in the same login.live.com context.
Honestly, I didn’t take this bug too seriously at first glance. To me, it was just a stronger Open Redirect that could jump to a few more Browser-Level Schemes, and still needed to be chained with other bugs. So the bug just sat in my notes for weeks… 🤦♂️
Sourceless Navigation: A New UXSS Primitive
The turning point was when I was going over the Chromium architecture one day and started thinking: The throttle is handled in the Browser Process, so the new navigation is initiated from the browser as well. However, the page we fired the throttle from, login.live.com, lives in the Renderer, and while investigating the logic, I couldn’t find any code tying the Browser and Renderer together. So… CURRENT_TAB refers to which tab, exactly?
To find this out, I ran a quick test and added a delay to the original trigger:
And during the delay, we manually switched the Edge to a new tab to see what would happen, and…
Boom! We got a UXSS on google.com, and that’s also the first bug of the whole chain. To fully understand the UXSS root cause, we dug into Chromium’s source code. We found that if Navigate() is called without specifying the source_contents (the Renderer source to bind to), Chromium would use the current active tab as the Renderer source automatically.
↓ Path: chromium/chrome/browser/ui/browser_navigator.cc
Though the code path requires params.disposition to be CURRENT_TAB and params.source_contents to be NULL, luckily, those are the defaults, so even Chromium’s own example code gets this wrong. I guess this should count as a new UXSS attack surface!
The 0-Click Challenges
The UXSS primitive we just got sounds great, but firing that still comes with quite a few challenges in practice. Here we list four we faced while weaponizing the UXSS, and how we cracked them with more and more bugs 😉.
1. The Throttle Must Be Fired from a Trusted Origin
The first challenge, as we mentioned earlier: this UXSS has to be fired from certain Microsoft sites. Though we could still find an XSS there to make that happen, that sounds like a painful job, so we’d keep it as a last resort. Instead, we decided to see if there was any low-hanging fruit in the origin check first.
We found that EdgeCAGuidanceNavigationThrottle uses GetLastCommittedURL() to retrieve the origin that fired the navigation, and then check it. At first glance, this looks like the correct way to do the validation. But take a second look, and you’ll notice Microsoft seems to have forgotten that a navigation can come not just from the current WebContents, but also from another window… So a simple window.open(...) is enough to bypass it!
↓ You can run this on any website to fire the throttle
2. The Popup Blocker
So far, we can bypass the origin check with a cross-window navigation. However, modern browsers don’t let webpages open new windows that easily nowadays, so here comes another new problem: the Popup Blocker would block window.open(...) by default.
To crack this, we kept reversing Microsoft Edge. And we found Microsoft, maybe for internal use, made its own hardcoded whitelist for popups… So a popup like the following would normally be blocked as expected:
But if you swap the URL for the whitelisted one… the window just appears. And then remember to navigate it back to the original site, and you’ve bypassed the Popup Blocker!
Thank you, Microsoft!
3. The Need for a Signed-In Profile
Now that we can fire EdgeCAGuidanceNavigationThrottle from wherever we want, the next step is to get past the Conditional Access checks. The code first requires Microsoft Edge to already have a signed-in User Profile, and then checks if ?email={email} matches the profile’s email. Both conditions have to be met before we reach the sourceless, browser-initiated navigation to finally trigger our UXSS primitive.
However, since we can’t assume the target environment has a signed-in profile in advance, we still need to find a way to make the attack work even if it doesn’t. While exploring Microsoft Edge, we came across a great feature called “OneAuth”. The feature lets any Edge WebView application automatically sign in with your current MSA credentials when visiting Microsoft-owned sites. And it turns out that starting with 25H2, Windows OOBE requires you to sign in with an MSA (unless you use the local-account workaround). So even if you’ve never created a User Profile in the browser, just visiting www.microsoft.com is enough for Edge to create one and sign you in automatically.
Thanks again, Microsoft!
4. The Need for Prior Knowledge of the Login Email
With the sign-in issue cracked, the last challenge is to get the victim’s email in advance. Of course, we could try some social engineering, or keep reversing Microsoft Edge to find another bug that leaks that for us. But since the automatic sign-in already sets several Microsoft cookies, we thought Web Hacking would be an easier way to steal the email. So we fired up Burp Suite and started looking for web vulnerabilities.
We spent a few hours and found several XSS bugs on Microsoft sites. While poking around, I noticed that assets.msn.com, which is embedded in edge://new-tab-page-online, returns the currently signed-in user’s email. And even more interesting, when I checked the response header, the site seemed to have a CORS misconfiguration…
So with a simple fetch(...) with credentials included, you can easily grab any Microsoft Edge user’s email from any website:
Putting It All Together!
So far, to make this primitive work, we’ve chained together:
- A Trusted Origin bypass caused by a missing cross-window Opener check
- A Popup Blocker bypass caused by a hardcoded whitelist
- A OneAuth feature that automatically signs you in
- A CORS misconfiguration that lets us steal the signed-in user’s email
Now we can finally trigger the UXSS without a single click! And even better, since this UXSS is initiated from the Browser Process (HandleRendererDebugURL in Mojo, if you’re interested), it can ignore any CSP entirely. That means we can run JavaScript on google.com, facebook.com, icloud.com, or whatever website we want!
Breaking Out of the Browser Sandbox
So we have a Universal XSS. Does it mean that we can do whatever we want? If we’re talking about Web Security, then yes. But our goal here is to get ACE (Arbitrary Code Execution), so UXSS alone is far from enough. And we still need to find a way to break out of the Browser Sandbox to turn this UXSS into code execution.
The first and most straightforward idea that came to my mind was: since we can run JavaScript on any website, or more precisely, any origin, why not just leverage privileged WebUI pages like edge://settings or edge://downloads to get ACE?
This naive approach was of course already mitigated by the Chromium developers. In the Renderer’s Mojo handler, there’s an extra check: if the origin belongs to one of the following privileged WebUIs, the Renderer simply ignores the JavaScript URL.
↓ Path: chromium/third_party/blink/renderer/core/frame/local_frame.cc
↓ Path: chromium/third_party/blink/renderer/platform/weborigin/scheme_registry.cc
Basically, Chromium has put every privileged WebUI you can think of on the not_allowing_javascript_urls_schemes blacklist. So at that point, I still got stuck for a long while. 😖
Arbitrary File Write via Immersive Reader
However, as we continued exploring Microsoft Edge, we found that Edge had its own Reading Mode called Immersive Reader. And unlike Chrome’s built-in Reading Mode, Immersive Reader seems to have been forked from Chromium early on, with Microsoft building on top of it. So instead of the old chrome-distiller:// or the blacklisted chrome-untrusted://, Edge uses a custom read:// scheme. And Microsoft seems to have forgotten to add this scheme to the blacklist as well, so our UXSS works on this privileged WebUI :)
Starting from this privileged WebUI, we can access lots of native C++ objects through JavaScript. And one of them called chrome.edgeFeedbackPrivate.zipDiagnosticLogFiles() looked particularly interesting
Auto-fetched and translated by AI Atlas. All rights belong to the original author.