作者:启明星辰ADLab
公众号:https://mp.weixin.qq.com/s/eowDUm2xmpXYK5w1mdD8JA

漏洞概述

2018年10月,启明星辰ADLab发现浏览器WebAssembly模块存在高危漏洞,并第一时间通报苹果和微软官方进行修复。该漏洞位于对应浏览器JavaScript引擎(JavaScriptCore/ChakraCore)与WebAssembly模块的接口,可同时影响Edge、Safari浏览器。

2019年3月25日,苹果发布了针对该漏洞的安全补丁(CVE-2019-6201);微软的对应漏洞补丁(CVE-2019-0607)已于2019年2月12日发布。提醒广大用户尽快将浏览器升级到最新版本。

漏洞影响范围

  • Microsoft Windows 10操作系统的Edge浏览器
  • Apple iOS/macOS操作系统的Safari浏览器
  • 其他平台上基于WebKit的组件和产品

漏洞简析

攻击者可通过精心构造的html网页,使用户在使用浏览器访问网页时触发漏洞。该漏洞在浏览器漏洞利用中可以直接作为fakeobj原语。通常addrof与fakeobj原语结合可以直接获得任意代码执行的能力,在一些特殊情况下,单独使用fakeobj原语也可以完成漏洞利用。

该漏洞的简要分析如下(以Safari/WebKit CVE-2019-6201为例):

WebAssemblyModuleRecord::link负责解析WebAssembly模块中的各个结构,在解析导出表时,有:

    case Wasm::ExternalKind::Global: {
        // Assert: the global is immutable by MVP validation constraint.
        const Wasm::Global& global = moduleInformation.globals[exp.kindIndex];
        ASSERT(global.mutability == Wasm::Global::Immutable);
        // Return ToJSValue(v).
        switch (global.type) {
        case Wasm::I32:
            exportedValue = JSValue(m_instance->instance().loadI32Global(exp.kindIndex));
            break;
        case Wasm::I64:
            throwException(exec, scope, createJSWebAssemblyLinkError(exec, vm, "exported global cannot be an i64"_s));
            return;
        case Wasm::F32:
            exportedValue = jsValue(m_instance->instance().loadF32Global(exp.kindIndex));
            break;
        case Wasm::F64:
            exportedValue = jsValue(m_instance->instance().loadF64Global(exp.kindIndex));
            break;
        default:
            RELEASE_ASSERT_NOT_REACHED();
    }

在加载导出的全局变量时,有Wasm::I32Wasm::I64Wasm::F32Wasm::F64四种类型,是WebAssembly标准中指定的数据类型(descriptor),分别表示32位、64位的整数和浮点数,在.wasm文件中用一个字节确定;随后根据变量类型的长度从.wasm文件中继续取出具体数据(value),封装成JSValue供JavaScript上下文使用。

以“case Wasm::F64为例”,debug版的代码会检查外来数据是否是一个符合IEEE754标准的双精度浮点数:

#define DoubleEncodeOffset 0x1000000000000ll

ALWAYS_INLINE JSValue::JSValue(EncodeAsDoubleTag, double d)
{
    ASSERT(!isImpureNaN(d));
    u.asInt64 = reinterpretDoubleToInt64(d) + DoubleEncodeOffset;
}
inline bool isImpureNaN(double value)
{
    // Tests if the double value would break JSVALUE64 encoding, which is the most
    // aggressive kind of encoding that we currently use.
    return bitwise_cast<uint64_t>(value) >= 0xfffe000000000000llu;
}

Release版本会在编译过程将isImpureNaN这一检查去掉,此时外来数据如果是一个NaN(Not a Number),例如0xffff000000888888,在通过加法(+DoubleEncodeOffset)封装成JSValue时会发生溢出,变成0x888888。由于Safari的boxing规则,这样的一个JSValue会被当作指针,因而发生类型混淆漏洞。

漏洞修补则顺其自然地把去掉的检查补回来:

case Wasm::F32:
-   exportedValue = jsValue(m_instance->instance().loadF32Global(exp.kindIndex));
+   exportedValue = jsNumber(purifyNaN(m_instance->instance().loadF32Global(exp.kindIndex)));
    break;
case Wasm::F64:
-   exportedValue = jsValue(m_instance->instance().loadF64Global(exp.kindIndex));
+   exportedValue = jsNumber(purifyNaN(m_instance->instance().loadF64Global(exp.kindIndex)));
    break;

Edge浏览器的漏洞和补丁也非常相似:

Var WebAssemblyInstance::CreateExportObject(WebAssemblyModule * wasmModule, ScriptContext* scriptContext, WebAssemblyEnvironment* env)
{
    Js::Var exportsNamespace = scriptContext->GetLibrary()->CreateObject(scriptContext->GetLibrary()->GetNull());
    for (uint32 iExport = 0; iExport < wasmModule->GetExportCount(); ++iExport)
    {
        Wasm::WasmExport* wasmExport = wasmModule->GetExport(iExport);
        Assert(wasmExport);
        if (wasmExport)
        {
            PropertyRecord const * propertyRecord = nullptr;
            scriptContext->GetOrAddPropertyRecord(wasmExport->name, wasmExport->nameLength, &propertyRecord);
            Var obj = scriptContext->GetLibrary()->GetUndefined();
            switch (wasmExport->kind)
            {
            ...
            case Wasm::ExternalKinds::Global:
                Wasm::WasmGlobal* global = wasmModule->GetGlobal(wasmExport->index);
                if (global->IsMutable())
                {
                    JavascriptError::ThrowTypeError(wasmModule->GetScriptContext(), WASMERR_MutableGlobal);
                }
                Wasm::WasmConstLitNode cnst = env->GetGlobalValue(global);
                switch (global->GetType())
                {
                case Wasm::WasmTypes::I32:
                    obj = JavascriptNumber::ToVar(cnst.i32, scriptContext);
                    break;
                case Wasm::WasmTypes::I64:
                    JavascriptError::ThrowTypeErrorVar(wasmModule->GetScriptContext(), WASMERR_InvalidTypeConversion, _u("i64"), _u("Var"));
                case Wasm::WasmTypes::F32:
-                   obj = JavascriptNumber::New(cnst.f32, scriptContext);
+                   obj = JavascriptNumber::NewWithCheck(cnst.f32, scriptContext);
                    break;
                case Wasm::WasmTypes::F64:
-                   obj = JavascriptNumber::New(cnst.f64, scriptContext);
+                   obj = JavascriptNumber::NewWithCheck(cnst.f64, scriptContext);
                    break;
                ...
                }
            }
            JavascriptOperators::OP_SetProperty(exportsNamespace, propertyRecord->GetPropertyId(), obj, scriptContext);
        }
    }
    DynamicObject::FromVar(exportsNamespace)->PreventExtensions();
    return exportsNamespace;
}

可以看到,在WebAssembly标准的实现中微软、苹果犯了类似的错误,导致漏洞的面貌也极其相似,漏洞原理也并不复杂。该漏洞是在WebAssembly功能实现时直接引入的,在Edge、Safari中已潜伏了2年。

另一方面,由于JavaScript引擎也无法良好地实现i64类型的WebAssembly变量,因此无论是Safari/WebKit还是Edge都拒绝对该类型及进行处理。MDN也在WebAssembly导出函数章节提到:“如果你尝试调用一个接受或返回一个i64类型导出的wasm函数,目前它会抛出一个错误,因为JavaScript没有精确的方式来标识一个i64。不过,这在将来可能会改变——在将来的标准中,将考虑新的i64类型。届时,wasm可以使用它”。

这给我们的启示:

  • 新技术、新标准会带来新的攻击面,标准的实现过程可能会伴随安全问题。
  • 不同模块耦合时可能会打破某模块内部的假设,需要谨慎对待。

根据该漏洞的特点,启明星辰ADLab已连续发现了若干漏洞和代码问题,并已通报厂商进行修复。

漏洞时间轴

  • 2018年10月30日,启明星辰ADLab向苹果提交漏洞;
  • 2018年11月6日,启明星辰ADLab向微软提交漏洞;
  • 2018年11月27日,苹果在WebKit代码库中修复漏洞;
  • 2019年1月24日,微软在ChakraCore代码库中修复漏洞;
  • 2019年2月12日,微软为Edge浏览器推送安全性更新,并披露CVE编号;
  • 2019年3月25日,苹果为Safari浏览器等产品推送安全性更新,并披露CVE编号。

安全建议

安装厂商推送的安全性更新,更新至最新版本。

为了方便社区贡献代码,Edge、Safari在内的常见浏览器产品往往将核心引擎组件开源,而开源代码仓库中的每次补丁提交均包含部分漏洞信息。因此在厂商正式披露漏洞并为产品推送补丁之前,黑客有一个构造漏洞POC的攻击时间窗。为了缩小这一时间窗,终端用户应及时安装厂商提供的安全性更新。

参考链接


Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/870/