Skip to content
KitploitKITPLOIT
工具博客
Log in
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
CVE-2023-6702 — Chrome 渲染器 1day RCE:异步堆栈追踪中的类型混淆漏洞(v8ctf submission) | Kitploit
工具/GitHubGitHub/kaist-hacking/cve-2023-6702
漏洞分析漏洞利用Web应用程序漏洞利用CTF学习与教育二进制利用
GitHubkaist-hacking/cve-2023-6702

CVE-2023-6702

Chrome 渲染器 1day RCE:异步堆栈追踪中的类型混淆漏洞(v8ctf submission)

查看仓库
86972年前Kitploit 审核通过

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

Chrome Renderer 1day RCE via Type Confusion in Async Stack Trace (CVE-2023-6702)

摘要

此漏洞允许远程攻击者在 Chrome 渲染器进程中执行任意代码。

在异步堆栈跟踪处理代码中存在一个不充分的类型检查。 它导致了 FunctionContext 和 NativeContext 之间的类型混淆,从而非法访问 JSGlobalProxy->hash 值。 通过堆喷射,攻击者能够注入伪造的异步堆栈帧,并构建 fakeobj 原语。 利用 fakeobj 原语,攻击者能够在 Chrome 渲染器进程中实现任意代码执行。

你可以查看 我们在 TyphoonCon 2024 上的幻灯片。

供应商 / 产品 / 版本

  • Google Chrome
  • 受影响版本:pre 120.0.6099.109
  • 修复版本:120.0.6099.109

时间线

  • 2020-05-13:漏洞引入 - [Promise.any] 实现 Promise.any 的异步堆栈跟踪
  • 2023-11-10:漏洞报告 - 安全:V8 Debug 检查失败:LAST_TYPE >= value
  • 2023-11-15:补丁 - [promises, async stack traces] 修复闭包已执行时的情况
  • 2023-12-12:公告 - https://chromereleases.googleblog.com/2023/12/stable-channel-update-for-desktop_12.html
  • 2024-01-12:v8CTF 提交 <-- 我们研究此漏洞的时间
  • 2024-02-23:漏洞报告公开

背景

异步堆栈跟踪

异步是 JavaScript 中最重要的特性之一。 过去,由于异步函数不会被捕获在错误堆栈中,调试异步代码的堆栈信息非常困难。 被挂起的异步函数存储在事件循环的回调队列中,而非调用栈中,因此错误堆栈不包含该异步函数。 为了解决这个问题,V8 提供了“异步堆栈跟踪”功能(自 V8 v7.3 起默认启用),以在错误堆栈中捕获异步函数。([v8 博客], [v8 文档])

Promise.all Resolve Element Closure

“Promise.all Resolve Element Closure”是一个辅助函数,用于解析 Promise.all 函数中的输入 promise。 Promise.all 函数接收一个 promise 数组,并返回一个在所有输入 promise 都 resolve 后 resolve 的 promise。 “Promise.all Resolve Element Closure”是 Promise.all 函数中每个输入 promise 的 resolve 处理器。 该函数的作用是解析输入 promise 并将 fulfillment 值存储到结果数组中。

关于此函数有两点需要注意:

  1. 它是一个内置的函数,不能直接从 JavaScript 代码中访问。
  2. 该函数的上下文被用作标记,用于检查函数是否已被执行。 在调用之前,它拥有 FunctionContext,在调用之后,它拥有 NativeContext。(v8 代码)

漏洞

漏洞类型: FunctionContext 与 NativeContext 之间的类型混淆

漏洞细节:

该漏洞可以通过在已执行的“Promise.all Resolve Element Closure”函数或类似的内置函数上捕获异步堆栈跟踪来触发。 在此漏洞利用中,我以“Promise.all Resolve Element Closure”函数为例。

当 JavaScript 代码中抛出错误时,V8 从堆栈中捕获错误堆栈,并将当前微任务中的异步堆栈帧追加到其中 [1]。

CallSiteBuilder builder(isolate, mode, limit, caller);
VisitStack(isolate, &builder);

// 如果启用了 --async-stack-traces 并且“当前微任务”是一个
// PromiseReactionJobTask,我们尝试用异步帧丰富堆栈跟踪。
if (v8_flags.async_stack_traces) {
    CaptureAsyncStackTrace(isolate, &builder);
}

CaptureAsyncStackTrace 函数 [[2]] 会查找 promise 链,并根据异步调用类型(例如 await、Promise.all、Promise.any)追加异步堆栈帧。

下面是处理 Promise.all 情况的 CaptureAsyncStackTrace 函数片段:

} else if (IsBuiltinFunction(isolate, reaction->fulfill_handler(),
                                Builtin::kPromiseAllResolveElementClosure)) {
    Handle<JSFunction> function(JSFunction::cast(reaction->fulfill_handler()),
                                isolate);
    Handle<Context> context(function->context(), isolate);
    Handle<JSFunction> combinator(context->native_context()->promise_all(),
                                isolate);
    builder->AppendPromiseCombinatorFrame(function, combinator);

    // 现在窥视 Promise.all() 的 resolve 元素上下文,以
    // 找到当所有并发 promise 都 resolve 时正在被 resolve 的 promise capability。
    int const index =
        PromiseBuiltins::kPromiseAllResolveElementCapabilitySlot;
    Handle<PromiseCapability> capability(
        PromiseCapability::cast(context->get(index)), isolate);
    if (!IsJSPromise(capability->promise())) return;
    promise = handle(JSPromise::cast(capability->promise()), isolate);
} else if (

在查找 promise 链时,如果 reaction->fulfill_handler 是“Promise.all Resolve Element Closure”内置函数,它会将异步 promise 组合器帧追加到错误堆栈中。 然后,通过访问 function->context->capability->promise 移动到下一个 promise。

问题在于该函数假设“Promise.all Resolve Element Closure”函数尚未被执行。 如果“Promise.all Resolve Element Closure”函数已被执行,其上下文会从 FunctionContext 变为 NativeContext。 这会导致 CaptureAsyncStackTrace 函数中出现 FunctionContext 与 NativeContext 之间的类型混淆。

构造 PoC:

触发漏洞的策略如下:

  1. 获取“Promise.all Resolve Element Closure”函数,这是一个内置函数。
  2. 显式调用“Promise.all Resolve Element Closure”函数,使其上下文从 FunctionContext 变为 NativeContext。
  3. 将“Promise.all Resolve Element Closure”函数作为一个新 promise 链中某个 promise 的 fulfill 处理器。
  4. 在该 promise 链中抛出一个错误,并捕获异步堆栈跟踪。

我使用了 Promise.all 的同步 promise 解析模式,以便在 JS 脚本层面获取“Promise.all Resolve Element Closure”函数。 我从 test262 测试用例中借鉴了该模式。

显式调用该函数后,为了触发漏洞,我使用了 [零成本异步堆栈跟踪文档][v8 docs] 中的示例代码来准备一个新的 promise 链,并将该内置函数设置为其中一个 promise 的 fulfill 处理器。

最后,当错误被抛出时,异步堆栈跟踪会以已执行的“Promise.all Resolve Element Closure”函数作为 fulfill 处理器被捕获,从而导致 FunctionContext 与 NativeContext 之间的类型混淆。

PoC 代码如下:poc.js

漏洞利用

(关于漏洞利用原语、利用策略、利用技术和利用流程,此处有定义。)

漏洞利用原语: fakeobj 原语

漏洞利用策略: 为了从类型混淆漏洞构建 fakeobj 原语,我使用了以下策略:

  1. 使用 JSPromise 对象进行堆喷射,使随机哈希数匹配到一个有效的 JSPromise 对象指针。
  2. 将哈希值视为有效的 JSPromise 对象指针,并注入伪造的异步堆栈帧。
  3. 使用 Error.prepareStackTrace 配合 getThis 方法来检索伪造的对象。

该漏洞导致 CaptureAsyncStackTrace 函数中出现 FunctionContext 与 NativeContext 之间的类型混淆。 它访问 Context->PromiseCapability->JSPromise 来构建下一个异步堆栈帧。 当漏洞被触发时,它访问 NativeContext->JSGlobalProxy->hash。 为了利用该漏洞,我将哈希值用作 JSPromise 对象指针。

从以下哈希生成函数可以看出,哈希值的范围是 (0, 0xfffff):

int Isolate::GenerateIdentityHash(uint32_t mask) {
  int hash;
  int attempts = 0;
  do {
    hash = random_number_generator()->NextInt() & mask;
  } while (hash == 0 && attempts++ < 30);
  return hash != 0 ? hash : 1;
}
pwndbg> p/x mask
$1 = 0xfffff

哈希值经过 SMI 标记,因此在内存中,它将存储为 hash << 1。 因此,内存中的值范围是 (0, 0xfffff << 1),且为偶数。

为了使随机哈希数匹配到一个有效的 JSPromise 对象指针,我们需要满足两个约束条件:

  1. 解释后的指针地址应为奇数。
  2. 我们必须在 (0, 0xfffff << 1) 范围内进行堆喷射。

根据这些约束,我使用 JSPromise 对象进行堆喷射,并左移 8 位使地址变为奇数,同时使用小的 for 循环以适应 (0, 0xfffff << 1) 的范围。

将随机哈希数匹配到一个有效的对象指针看起来成功率较低。 为了提高可靠性,我使用了 iframe 技术。 由于站点隔离,来自不同网站的页面运行在不同的进程中。 因此,我创建了一个不同域的 iframe,并在该 iframe 中运行漏洞利用,以避免主进程崩溃。

移动到 promise 链中的下一个 promise 后,程序会检查该 promise 的有效性,并尝试根据异步调用类型追加异步堆栈帧。

  while (!builder->Full()) {
    // 检查 {promise} 是否未 settle。
    if (promise->status() != Promise::kPending) return;

    // 检查 {promise} 上是否恰好有一个 PromiseReaction。
    if (!IsPromiseReaction(promise->reactions())) return;
    Handle<PromiseReaction> reaction(
        PromiseReaction::cast(promise->reactions()), isolate);
    if (!IsSmi(reaction->next())) return;

    // 检查 {reaction} 的 fulfill 处理器是否为已知的异步函数或
    // 异步生成器延续之一。
    if (IsBuiltinFunction(isolate, reaction->fulfill_handler(),
                          Builtin::kAsyncFunctionAwaitResolveClosure) ||
        IsBuiltinFunction(isolate, reaction->fulfill_handler(),
                          Builtin::kAsyncGeneratorAwaitResolveClosure) ||
        IsBuiltinFunction(
            isolate, reaction->fulfill_handler(),
            Builtin::kAsyncGeneratorYieldWithAwaitResolveClosure)) {
      // 现在窥视处理器的 AwaitContext 以获取
      // 异步函数的 JSGeneratorObject。
      Handle<Context> context(
          JSFunction::cast(reaction->fulfill_handler())->context(), isolate);
      Handle<JSGeneratorObject> generator_object(
          JSGeneratorObject::cast(context->extension()), isolate);
      CHECK(generator_object->is_suspended());

      // 追加与 {generator_object} 对应的异步帧。
      builder->AppendAsyncFrame(generator_object);

我们选择了 kAsyncFunctionAwaitResolveClosure 情况,因为 AppendAsyncFrame 函数的参数 generator_object 是完全可控的。

通过设置适当的伪造对象(如 PromiseReaction、Function、Context、JSGeneratorObject)来满足条件,我们可以通过调用 builder->AppendAsyncFrame(generator_object) 注入我们伪造的异步帧。 我们可以从终端查看注入的伪造异步帧。

下载工具