前言

早年在浏览器大战期间,有远见的Chrome认为要运行现代Web应用,浏览器必须有一个性能非常强劲的JavaScript引擎,于是Google自己开发了一个高性能的开源的JavaScript引擎,名字叫V8。在2009年,Ryan正式推出了基于JavaScript语言和V8引擎的开源Web服务器项目,命名为Node.js。

从此JavaScript也可以在后端服务器开发,由于已经有很多的JavaScript开发人员,所以Node很快流行起来了。Node.js可以安装在linux、mac/windows平台上, npm其实是Node.js的包管理工具(package manager),它可以让开发人员方便的安装、卸载js模块,并且自动的解决依赖关系。Node.js可以自己作为服务器,监听端口,处理客户端的请求,由于node.js本身是单线程的,为了缓解可能的dos攻击,可以采用nginx第三方服务器部署成负载均衡的多实例方式。目前node.js社区已经诞生了很多优秀的web框架,比如 Express是第一代最流行的web框架,koa是Express的下一代基于Node.js的web框架。本文主要从安全的角度来分析node.js的特性。

0×00示例代码

本文的事例代码采用了express框架,共分为以下几个部分:有3个文件和1个文件夹。

file.html                    //稍后用到的上传页面

index.js                     //主要的文件,包含了xss、ssrf、文件长传、sql等例子

package.json            //记录了用到的js模块等信息

node_modules        //是一个文件夹,js模块所在的目录

Package.json

{

  “dependencies”: {

    “body-parser”: “^1.18.2″,

    “cookie-parser”: “^1.4.3″,

    “express”: “^4.16.2″,

    “multer”: “^1.3.0″

  }

}

Index.js

var express = require(‘express’);

var child_process = require(‘child_process’);

var helmet = require(‘helmet’);

var needle = require(‘needle’);

var app = express();

var fs = require(“fs”);

var multer = require(‘multer’);

var bodyParser = require(‘body-parser’);

app.use(helmet());

app.use(multer({ dest: ‘/tmp/’}).array(‘image’));

app.use(express.static(‘public’));

app.get(‘/’, function (req, res) {

   res.send(‘Hello World’);

})

app.get(‘/file.html’, function (req, res) {

   res.sendFile( __dirname + “/” + “file.html” );

})

app.get(‘/eval’,function(req,res){

    res.send(eval(“req.query.q”));

})

app.post(‘/file’,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 ) );

       });

   });

})

app.get(‘/xss’,function(req,res) {

   res.send(req.query.q);

})

app.get(‘/ssrf’,function(req,res){

   var url=req.query['url'];

   needle.get(url)

   console.log(‘new request:’+url);

})

app.get(‘/rce’, function (req, res) {

   child_process.exec(req.query.q);

   console.log(res);

})

var server = app.listen(50000, function () {

  var host = server.address().address

  var port = server.address().port

  console.log(“应用实例,访问地址为 http://%s:%s“, host, port)

})

0×01 设置安全的HTTP头

在Node.js中可以通过强制设置一些安全的HTTP头来加强网站的安全系数,比如以下:

Strict-Transport-Security       //强制使用安全连接(SSL/TLS之上的HTTPS)来连接到服务器。

X-Frame-Options                 //提供对于点击劫持的保护。

X-XSS-Protection                //开启大多现代浏览器内建的对于跨站脚本攻击(XSS)的过滤功能。

X-Content-Type-Options          // 防止浏览器使用MIME-sniffing 来确定响应的类型,转而使用明确的content-type来确定。

Content-Security-Policy         // 防止受到跨站脚本攻击以及其他跨站注入攻击。

目前Helnet第三方模块已经帮开发人员设置好了,直接将它引入到我们的系统就可以了。

var express = require( ‘express’); 

var helmet = require( ‘helmet’);

var app = express();

app.use(helmet()); 

客户端请求网站时:

israbye FreeBuf.COM

0×02代码执行

熟悉php渗透的朋友都领教过eval()在木马文件里的妙用。js也有eval()函数,由于强大的功能甚至被誉为“魔鬼”。它在js中的功能跟php是差不多的,即动态的执行代码。假如客户端的输入直接丢到eval函数里面执行,轻则产生各种xss弹框,但通常攻击者都会用它调用关键函数来执行系统命令。

app.get(‘/eval’,function(req,res){

    res.send(eval(“req.query.q”));

})

除了eval函数能动态执行代码,setInteval、setTimeout、 new Function等函数也有相同的功能,因此在使用的使用要小心谨慎。

0×03命令执行

在Node.js中child_process.exec命令调用的是/bin/sh,因此它是一个bash解释器,  可以执行系统命令,若其直接接受外部参数 则可能造成RCE漏洞。

app.get(‘/rce’, function (req, res) {

 

   child_process.exec(req.query.q);

   console.log(res);

})

成功执行了运行计算器的命令:

israbye FreeBuf.COM

0x04xss

Node.js不像java有很强大的过滤器,过滤用户的有害输入、缓解xss十分方便。但是可以通过设置HTTP头中加入X-XSS-Protection,来开起浏览器内建的对于跨站脚本攻击(XSS)的过滤功能。由于本身没有xss防护机制 ,若是未经过滤直接显示外部的输入则导致XSS。

如下:

app.get(‘/xss’,function(req,res) {

   res.send(req.query.q);

})

程序直接将用户的输入显示到前端页面:

israbye FreeBuf.COM

0×05 sql注入

Node.js的网站注入漏洞很少。Node.js通常与mysql/mongodb搭配使用,因为sql注入的漏洞危害很高并且存在多年了,一些新出现的语言如openresty+lua/node.js等天生会规避掉这种安全问题。它们通常都采用了占位符或者叫参数化查询来与数据库交互。node.js 原生的与数据库交互代码如下:

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注入的。如下:

select * from admin  where id=$id

0×06 ssrf

ssrf漏洞在存在于大多数的编程语言中,node.js也不例外,只要web系统接收了外界输入的URL,并且通过服务端程序直接调用就会造成相应的漏洞。

app.get(‘/ssrf’,function(req,res){

   var url=req.query['url'];

   needle.get(url)

   console.log(‘new request:’+url);

}) 

请求地址:

israbye FreeBuf.COM

0×07 文件上传

Node.js的网站由于特有的路由规则,它的的上传问题虽然不像php、jsp、asp等脚本语言,若攻击者上传若未经过滤的脚本,便可轻松的拿到shel。但是代码中若存在路径跳转漏洞,攻击者可以直接将shell脚本木马上传到/etc/rc.d等启动项下面,或者是直接上传相应的index.js文件覆盖到第三方模块express等目录下,通过精心构造的js文件也能实现命令执行的目的。

app.post(‘/file’,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 ) );

       });

   });

})

File.html

<html>

<head>

<title>File</title>

</head>

<body>

Upload File: <br />

<form action=”/file” method=”post” enctype=”multipart/form-data”>

<input type=”file” name=”image” size=”50″ />

<br />

<input type=”submit” value=”upload” />

</form>

</body>

</html>

israbye FreeBuf.COM

israbye FreeBuf.COM

0×08 NPM

任何人都可以创建模块发布到npm上,供别人调用,虽然这为开发者带来了一定的便利性,但必然隐藏着安全隐患,假如一不小心使用了不安全的第三方模块后果可想而知了,比如前段时间闹得沸沸扬扬的node-serialize模块所引起的远程代码执行漏洞(cve-2017-5914)。现在有一款NSP 工具可以帮助检查第三方模块现有漏洞。

npm i nsp –g //安装nsp

nsp check 要检查的package.json //检查是否有漏洞

总结:

Node.js最为人诟病的就是单线程,大部分任务都在一个线程中完成,单线程虽然省去了频繁的切换线程,也不存在资源互占的问题,但面对cpu密集型的任务就力不从心了。Node.js具有异步机制,可以把一些耗时算法丢人eventloop等待下个事件循环再做,但任务量大了相比java等多线程机制的语言还是容易造成服务器崩溃。即使有这个缺点,但并不影响Node.js的流行。仗者JS在前端的绝对的统治地位,我们有理由相信Node.js会越来越流行,研究Node.js的同行注定也会越来越多。

*本文作者:m09046105,参考文章:https://segmentfault.com/a/1190000003860400