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

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

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)

查看仓库
86922年前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]。

root@kitploit:~
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 函数片段:

root@kitploit:~
} 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):

root@kitploit:~
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;
}
root@kitploit:~
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 的有效性,并尝试根据异步调用类型追加异步堆栈帧。

root@kitploit:~
  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) 注入我们伪造的异步帧。 我们可以从终端查看注入的伪造异步帧。

root@kitploit:~
Error: Let's have a look...
    at bar (../../../../fake_frame.js:168:15)
    at async foo (../../../../fake_frame.js:163:9)
    at async Promise.all (index 0)
    at async Array.sloppy_func (../../../../fake_frame.js:1:1)

这里是 fake_frame.js 代码。

注入伪造的异步帧后,我使用了 Error.prepareStackTrace 配合 getThis 方法来获取错误对象的 receiver(在本例中为 JSGeneratorObject)。 通过 receiver,我们可以从堆中检索伪造的对象(fakeobj 原语)。

漏洞利用流程: 我使用了典型的 V8 漏洞利用流程。

  1. 利用 fakeobj 原语,我植入并检索了伪造的 OOB 数组。
  2. 使用伪造的 OOB 数组,我构建了 caged_read/caged_write 原语。
  3. 为了实现 RCE,我参考了 Google CTF 2023 中共享的 技术。 为了逃逸 V8 沙箱,我破坏了一个 BytecodeArray 对象以执行任意字节码。 通过使用 Ldar/Star 指令配合越界访问,我们可以读写堆栈。 为了泄露 chrome 二进制基址,我从堆栈中读取了一个返回地址,以泄露基址的低 32 位,并读取了一个 libc 堆指针以获得地址的高 16 位。 然后,我破坏了栈指针以进行栈翻转,并执行 ROP 链来实现 RCE。

这里是完整的漏洞利用代码:index.html 和 exploit.html 它已在 Chrome 118.0.5993.70 上测试通过,该版本是 v8CTF M118 的目标版本。

致谢

KAIST Hacking Lab 的 Haein Lee

下载工具