这两周苹果的安全通告在圈子里讨论得不少,原因很简单,两个 WebKit 漏洞都是在定向攻击里被真实利用过的,其中一个还和谷歌 ANGLE 项目此前修掉的某个漏洞同源。翻译成大白话就是:攻击者已经拿着这些“武器”上过战场了,如果你的设备还停留在旧版本,那 Siri 这种日常入口都可能是被突破的那扇门。无论你是普通 iPhone 用户,还是正在做跨平台浏览器、Web 容器、甚至只是用 WKWebView 内嵌页面的开发者,这次的修复都应该认真看一遍。这篇文章会把事件背景、漏洞机理、补丁分析方法和我的排查经验整理成完整笔记。
1. 事件全貌:这次修复解决的不只是“崩溃”问题
1.1 两个漏洞分别落在哪里
先说第一个,也是最“经典”的漏洞类型,落在 WebKit 的 JavaScriptCore 组件上。JavaScriptCore 是 Safari 用来解析、解释、编译 JavaScript 的引擎,里面有一条 JIT 编译路径,为了追求性能,会把热点 JavaScript 代码直接编译成机器码。JIT 引擎最怕的就是对象生命周期管理出岔子,这次涉及的正是典型的 use-after-free 问题,对象在内存里被释放之后,仍然有代码路径去引用它。攻击者一旦控制住这块“已经被释放的内存”,就可以用精心伪造的数据结构去替换原来的对象,后续代码顺着引用关系走下去时,执行流程就会被劫持。这类漏洞在浏览器引擎里基本属于“老熟人”,但老熟人并不意味着好对付,利用起来往往相当稳定。
第二个漏洞出现在 WebKit 集成的 ANGLE 组件上。ANGLE 本身是谷歌发起的一个开源图形转译层项目,全称是 Almost Native Graphics Layer Engine,主要作用是把 OpenGL ES 和 WebGL 应用里的着色器语言翻译成各平台底层的原生图形 API 调用,比如 Windows 上的 Direct3D、macOS 上的 Metal。WebKit 在做 WebGL 支持的时候,不是“参考”了一下 ANGLE 的设计思路,而是在代码层面直接把大量 ANGLE 源码引进了自己的仓库。这次同源漏洞的位置就在 ANGLE 的着色器编译环节,某处缓冲区长度计算不严谨,导致畸形输入可以越过边界读写内存。别看它藏在图形底层,普通用户打开一个带 WebGL 内容的网页,触发链路就可能从一段畸形着色器代码开始。
1.2 为什么“被在野利用”是严重信号
普通人看到“修复漏洞”四个字早就麻木了,但这次的关键词是“在定向攻击中被利用”。这和那种由安全研究员用 fuzzer 随机砸出来的漏洞完全是两个性质。fuzzer 发现的漏洞,攻击者不一定知道,用户在更新之前还能有一段缓冲期;而“被利用”的意思非常直白:补丁发布之前,已经有攻击者拿着这个漏洞在真实目标身上试过手了。
更值得注意的是,两个漏洞是同一时间被披露的。在安全圈,这种组合经常不是巧合,而是攻击者排好的组合拳。过程通常是这样:先用一个 WebKit 漏洞在渲染进程里打出一个内存破坏点,拿到执行代码的能力;再借助另一个漏洞绕过更上游的防护机制,或者完成沙盒到系统核心的跳跃。所以这次公告虽然写的是“两个漏洞”,实际威胁等级要按“一条完整攻击链”来评估。对普通用户来说,这个信号的直接含义是:你访问一个被恶意投递的网页,哪怕什么都没点,Safari 的渲染进程也可能已经被攻击者接管。
1.3 影响范围评估:谁受影响,影响有多广
这次影响范围绝不仅仅是“iPhone 上的 Safari 浏览器”。苹果生态里所有依赖 WebKit 的入口都在攻击面范围内,包括 iOS 和 macOS 上的 Safari、App 内嵌的 WKWebView 控件,以及各类使用系统 WebKit 框架做渲染的混合应用。很多用户以为我只看浏览器就行,实际上像 App 内广告 SDK、第三方登录页、甚至邮件 App 预览 HTML 邮件这类场景,都会把网页内容交给 WebKit 去渲染。
还有一类存量用户容易成为攻击者偏爱的目标:老旧系统里继续沿用 UIWebView 的应用。UIWebView 虽然是苹果官方早就标识为废弃的组件,但存量设备里仍有不少应用在用它,因为系统更新滞后或者应用长期不维护。攻击者对这类“软柿子”可以说是来一个捏一个。所以这次补丁虽然挂在 WebKit 名下,真正的覆盖面是苹果生态里所有调用了相关渲染进程的层面,任何一环没有更新,整个链条都可能因此被撕开。
2. 漏洞机理深挖:浏览器内核为何成了“薄弱环节”
2.1 WebKit 的分层架构与浏览器攻击面
把 WebKit 拆开看,核心三大块分别是 WebCore、JavaScriptCore 和渲染合成部分。WebCore 负责 HTML、CSS、DOM 这些网页标准的解析与建模,JavaScriptCore 负责脚本执行,再往下渲染合成部分会把页面元素转成 GPU 可执行的绘制指令。为了不让浏览器“开了个口子就全家遭殃”,现代系统还给网页渲染加了一层沙盒进程。但沙盒并不是金钟罩,它限制的是渲染进程对系统资源的访问权限,并没有限制攻击者在渲染进程内部对内存进行任意读写。
从攻击者角度看,一张网页就是一个超大的输入面。HTML 标签、CSS 选择器、JavaScript 数组、WebGL 着色器,全部都会经过解析、编译、内存分配与释放,其中任何一步都可能埋着越界、空指针、逻辑竞争的雷。有一个很直观的比喻:浏览器内核就像一座每天要接待几百万陌生访客的城堡,访客可以带任何包裹进来,城堡守卫只能拆一部分查。攻击者要找的就是哪个门卫看漏了眼。
2.2 ANGLE 在 WebKit 中的角色
ANGLE 的定位最容易理解成“翻译官”。浏览器拿到的 WebGL 代码是用 GLSL 写的,GLSL 是一种语法接近 C 的高层着色器语言,开发者用它在 GPU 上实现光照、阴影、特效。可是不同操作系统底层的图形 API 并不一样,Windows 主流用 Direct3D,苹果系更倾向 Metal。为了让开发者写一份 GLSL 就能全平台跑起来,浏览器内部就必须有一层东西把这些代码翻译成目标平台能懂的语言。ANGLE 就是这个翻译器。
翻译器这种组件,最怕的就是“输入可控但校验不严谨”。GLSL 编译器的工作流程包括字符串解析、语法树构建、类型推导、代码优化和寄存器分配,每一步都可能用到临时缓冲区、符号表、数组下标。一旦某个长度计算差了一个字节,攻击者就能用一段恶意着色器代码制造越界写入。而用户这边看到的情况,仅仅是打开了一个包含 WebGL 交互页面的普通网站,完全不会察觉到后台发生了什么。
2.3 “同源”到底意味着什么
“其中一个和谷歌 ANGLE 漏洞同源”这句话被很多媒体直接转载,但真正理解的人不多。有人以为苹果抄袭了谷歌的代码然后被发现,其实真实情况更朴素:WebKit 里那部分 ANGLE 代码本来就是从谷歌 ANGLE 仓库迁移过来、再持续同步的。当谷歌的 ANGLE 项目发现漏洞并修复之后,如果 WebKit 这边没有及时把修复 merge 进自己的代码树,那么 WebKit 里残留的代码就仍然带着这个洞。两个项目,同一段祖先代码,同一个坑,这就是同源的意思。
这种“上游修复、下游滞后”的现象在大项目里太常见了。组件一旦被多个项目引用,维护者就要同时跟踪几条同步线。这次苹果补丁的发布,等于把“同步滞后”四个字写成了公告级别的案例。它提醒所有集成了开源组件的团队,光靠上游修复是不够的,你的分支里那个旧版本如果不跟上,漏洞就会一直以一个“合法组件”的身份存在于你的产品中。
2.4 从内存破坏到代码执行的常规路径
把漏洞变成可利用武器,通常要经过四个步骤。第一步是通过越界读泄漏渲染进程的内存布局,拿到模块基址或者 JIT 引擎生成的代码页位置,为后续精确布局做准备。第二步是通过越界写或 use-after-free 把伪造的对象指针放进被释放的内存区域,等 JavaScript 继续使用这块内存时,程序的正常流程已经被带偏。第三步是利用 WebAssembly 或 JIT 代码喷射,把攻击者想要执行的机器码写进可执行内存;最后一步才是跳转过去接管进程控制权。
在这个链条里,两个漏洞往往不是各自独立的“任意门”,而是同一把钥匙的两个锯齿。一个漏洞负责撕开口子,另一个负责清除障碍。比如 JavaScriptCore 的漏洞可能在渲染进程里拿到代码执行能力,ANGLE 的漏洞能够穿透进程边界或者绕过某些检查,两者组合起来才能形成一条从“打开网页”到“完全控制设备”的完整攻击链。下面这张表梳理了各阶段的技术目标:
| 攻击阶段 | 常用技术 | 与本次漏洞的关系 |
|---|---|---|
| 入口构造 | 恶意网页 / 畸形 WebGL 着色器 | 两个漏洞均在渲染进程内被触发 |
| 信息泄漏 | 越界读、类型混淆 | JavaScriptCore 或 ANGLE 的越界能力提供数据 |
| 控制流劫持 | 堆喷 + 伪造对象 | use-after-free 指向攻击者控制的布局 |
| 纵深突破 | 第二漏洞 + 沙盒逃逸 | 同源组合用于绕过后置防线 |
3. 补丁分析与实操验证:安全团队如何确认影响
3.1 补丁读取:从公告到 diff 的一小时
如果你是平台安全团队的负责人,收到苹果这套安全更新的第一反应不应该直接去点“升级设备”,而是先把公告解读清楚。官方通告通常只写组件、漏洞类型和严重等级,真正有用的细节要看开源仓库里的 commit diff。WebKit 项目是开源维护的,相关修复提交会包含具体的代码变更,比如某块内存释放逻辑里增加了一个条件判断,或者某个缓冲区长度计算公式从<改成了<=。
看 diff 的核心价值在于理解作者选择了什么样的修复方式。修正 off-by-one 可能只需要加一行,但为什么是加这一行而不是改别的地方,背后往往藏着对漏洞成因的精确判断。有时候,你还能从 diff 里反推出攻击者的利用思路:开发者修改了某个锁的位置、调整了某个对象的生命周期回调,说明漏洞利用大概率就发生在那个时间点上。安全团队如果能把这些细节吃透,后续排查同类问题会快很多。
3.2 受控环境中的复现思路
复现这个动作不是鼓励大家去攻击真实系统,而是在隔离环境里用构造好的畸形输入来验证“修复前会不会崩、修复后是否稳定”。以 ANGLE 那个漏洞为例,可以准备一段包含极端变量数量、复杂嵌套表达式的 GLSL 着色器代码,喂给 WebKit 的渲染组件,观察是否存在越界崩溃。这里有个基本原则:优先用官方构建的测试外壳,不要直接拿线上浏览器做实验,因为测试外壳的堆分配更可控,崩溃信息更集中。
我在类似场景里通常会开启 ASAN 这类内存检测工具,构建命令大概长这样:
cd WebKit ./Tools/Scripts/build-webkit --debug --enable-asan再准备一个本地测试页面,把恶意 GLSL 代码包装成 WebGL 上下文调用的形式:
<!DOCTYPE html> <html> <body> <canvas id="c" width="64" height="64"></canvas> <script> const gl = document.getElementById('c').getContext('webgl'); const src = ` #version 300 es precision highp float; uniform float u_data[65536]; void main() { gl_Position = vec4(u_data[65535]); } `; const shader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(shader, src); gl.compileShader(shader); </script> </body> </html>上面这个例子只是模拟“大量数据 + 越界输入”的通用验证思路,并非本次漏洞的具体触发代码。用 ASAN 构建的测试外壳跑完后,如果漏洞确实存在且被触发,内存检测工具会直接输出越界的函数名和调用栈,后续分析比手工翻汇编高效得多。
3.3 更新部署流程与用户侧验证
对普通用户来说,正确动作就是升级系统。iOS 用户去“设置 > 通用 > 软件更新”,macOS 用户到“系统设置 > 通用 > 软件更新”,看到包含安全修复的版本就安装,不要拖。有些用户会担心新系统是不是更耗电、更卡,这种担心可以理解,但面对“已在定向攻击中被利用”的漏洞,更新的优先级一定要高于性能焦虑。
对开发者来说,情况要多走一步。如果你的 iOS 或 macOS 应用是用 WKWebView 加载网页内容,并且没有自定义 WebKit 分支,那系统更新到位之后,应用里所有 WKWebView 实例都会自动获得修复,开发者不需要重新提审。但如果你在工程里直接集成了自定义的 WebKit 构建,或者使用了某些派生框架,就必须把新补丁同步到自己的分支、重新编译、再发版。企业批量管理设备的情况下,还要通过 MDM 观察系统版本的分布,最好记录一条类似下面的状态清单:
| 角色 | 动作 | 验证方式 |
|---|---|---|
| 普通用户 | 安装系统更新 | 软件更新界面显示已是最新 |
| App 开发者 | 确认系统动态库覆盖 | 真机系统版本 ≥ 修复版本 |
| 自定义内核维护者 | 合并上游 commit 并重新构建 | 构建产物通过回归测试 |
4. 常见问题与排查技巧:我踩过的一些坑
4.1 误区:安全补丁只影响浏览器
这个误区我在很多团队里都见过。一听到“WebKit 漏洞”,业务负责人第一反应是“那是苹果自家 Safari 的事,和我们有什么关系”。实际上,大量 App 都在使用 WKWebView,无论是内嵌活动页面、加载 web 版协议,还是接入第三方广告 SDK,浏览器引擎都是那块绕不开的公共底座。系统如果没有更新到包含修复的版本,攻击者完全可以借一个恶意网页,攻破 App 的渲染进程。
我参与过的一次安全评估里,某 App 用 WKWebView 做用户协议页展示,团队认为“这个页面是我们自己写的,很安全”。结果测试时发现,只要在这个页面里注入一段带畸形 WebGL 代码的第三方素材,渲染进程就直接崩溃了。问题不在于 App 业务逻辑写得好不好,而在于 WebKit 组件本身存在未修复漏洞。那次之后,我给他们的建议一直是:应用的安全清单上永远要保留一项“依赖的系统组件版本是否已是最新”。
4.2 误区:ANGLE 漏洞只影响谷歌产品
ANGLE 是由谷歌发起的没错,但它已经不是一个只属于谷歌的项目了,WebKit、多个浏览器内核以及各种图形渲染框架都在用它的代码。只要你的产品支持 WebGL 渲染,几乎就等于碰过了 ANGLE 的编译链路。这次的苹果补丁再次说明了这一点:上游项目修复了一个漏洞,但下游如果同步不及时,漏洞就会继续潜伏在自己的代码库里。
我特别想提醒做技术选型的朋友,不要因为某个组件“出身于某家大型公司”就觉得它自带光环、绝对可靠。真正的可靠性来自两条:一是组件上游维护者响应漏洞的速度,二是你把上游修复同步到自己分支的速度。前者是别人的事,后者才是你的安全责任。
4.3 排查技巧:如何判断系统是否已修复
这里分享几个可以直接上手的排查方法。macOS 上打开终端,执行:
sw_vers -productVersion system_profiler SPSoftwareDataType | grep "System Version"拿到系统版本号之后,查一下对应版本是否已经包含本次 WebKit 安全更新。iOS 设备可以到“设置 > 通用 > 关于本机”查看系统版本,企业设备则通过 MDM 的合规策略批量检查版本分布,这是比较靠谱的方式。
还有一种排查路径是看崩溃日志。修复之前,如果恶意页面在设备上触发过 WebKit 漏洞,你会在日志中看到 WebContent 进程的异常退出记录;修复后的版本在同样输入下不会崩溃。macOS 的“控制台”App 和~/Library/Logs/DiagnosticReports目录都可以查看相关记录。不过要记住,能通过崩溃日志判断的前提是你已经有一个“修复前崩溃、修复后正常”的对照样本,否则单看一条崩溃记录很难区分是漏洞触发还是普通 bug。
4.4 实操心得:一次代码同步不及时的教训
我印象很深的一次经历,是给一个跨平台浏览器项目做合规审查。当时项目里还带着一个旧版 ANGLE 模块,负责人说“改动太大,暂时不动图形这块”。结果在评估时,有人用一段构造过的 WebGL 着色器代码,很轻松就让渲染进程在测试环境里段错误了。后来花了半天把上游补丁合并进来,问题立刻消失。这和这次苹果公告里的“同源漏洞”几乎是一个模子刻出来的:不是开发团队能力不行,而是同步机制出了问题。
从那以后,我形成了一个习惯:只要项目里用到了开源组件,就一定要建立两条机制并行。一条是上游安全公告的自动追踪,另一个是周期性代码合并的硬性任务,不能等漏洞爆了才去查上游是不是修过。这次苹果修复的两个 WebKit 漏洞,表面上是苹果和谷歌各自项目之间的事,本质上是在给所有集成了第三方代码的团队做一次免费提醒。
如果让我用一句话总结这次的苹果 WebKit 修复事件,那就是:一次“同源”修复,给所有使用开源组件的团队提了个醒。真正的风险往往不是新漏洞本身,而是旧代码没有被及时同步更新。作为普通用户,看到系统更新推送就尽量早点装,不用等“没什么大问题再说”;作为开发者,回到最基础的版本管理动作上,每次上游有安全补丁时,先别急着说改动大、工作忙,把同步窗口压缩到最小,才是真正省钱省力的做法。