作者:且听安全
原文链接:https://mp.weixin.qq.com/s/c0X8Ct2I2SP-H_pioMM12Q
前端时间 Sophos Firewall 爆出了一个认证绕过漏洞 CVE-2022-1040 ,最近在深入分析 Sophos 服务架构的同时,完整复现了该漏洞。主要是在 User Portal
及 Webadmin
两个接口存在认证绕过漏洞,漏洞巧妙利用了 Java 和 Perl 处理解析 JSON 数据的差异性,实现了变量覆盖,从而导致认证绕过及命令执行。漏洞适用范围为 Sophos Firewall v18.5 MR3 及以下版本。
首先从官网下载老版本虚拟机。本文研究下载版本为 VI-18.5.2_MR-2.VMW-380
:
按照提示很容易完成安装。Sophos 的 Web 接口主要通过 Java 实现,由启动命令可以看出 Sophos 使用的是 openjdk 环境,如果在 Java 启动参数中直接添加调试参数,将会出现找不到 libjdwp.so
动态链接库的错误:
可以通过自行上传完整 JDK 来解决这个问题:
重启 Java 服务即可看到监听的端口。
Sophos 中的服务架构不是很复杂,主要使用了 Apache 、 Jetty 等 web 服务,第一层语言为 Java 通过网络通信的方式与后端 Perl 服务进行交互:
Apache 的启动命令如下:
apache -d /_conf/httpd -DFOREGROUND
进入 /_conf/httpd
目录,存放有 Apache 的配置文件,通过分析 httpd.conf
,了解 Apache 引用了 /cfs/web/apache/httpd.conf
配置:
Define userportal_listen_port 65004
Define webconsole_https_port 65003
Define SSLCertificateFileWithPath "/conf/certificate/ApplianceCertificate.pem"
Define SSLCertificateKeyFileWithPath "/conf/certificate/private/ApplianceCertificate.key"
Define https_cert_valid true
从配置中可以看出 Apache 开放了两个主要的端口 userportal 65004
和 webconsole 65003
:
在 Apache 配置目录下搜索 ProxyPass
,找到的代理转发配置如下:
./ssl.conf:53: ProxyPass /webconsole/images !
./ssl.conf:54: ProxyPass /webconsole/css !
./ssl.conf:55: ProxyPass /webconsole/javascript !
./ssl.conf:56: ProxyPass /webconsole http://localhost:8009/webconsole
./ssl.conf:57: ProxyPassReverse /webconsole http://localhost:8009/webconsole
./userportal-static.conf:64: ProxyPass /userportal/images !
./userportal-static.conf:65: ProxyPass /userportal/CRSSL !
./userportal-static.conf:66: ProxyPass /userportal http://localhost:8009/userportal
./userportal-static.conf:67: ProxyPassReverse /userportal http://localhost:8009/userportal
代理转发策略将 webconsole
和 userportal
端口分别代理到 8009
端口的不同 URL :
ProxyPass /webconsole http://localhost:8009/webconsole
ProxyPass /userportal http://localhost:8009/userportal
Jetty 配置文件为 /usr/share/jetty/start.ini
,开启了本地服务的 8009
端口:
Jetty 的启动参数在 /usr/bin/jetty
脚本中配置,如果要修改可直接修改该文件最后 Java 执行部分。
CSC 是 Sophos 的主要服务之一,主要负责启动各个服务进程及提供 API 接口供其他程序服务调用。其启动命令为:
csc -L 3 -w -c /_conf/cscconf.bin
CSC 为标准的 ELF 32bit 可执行程序,可通过逆向分析其中功能。cscconf.bin
中在 CSC 程序中有调用解压,猜测是一个加密压缩包。
/usr/bin/csc
由 C 语言编写,负责启动加载其他的服务以及加载 Perl 代码,在虚拟机中 CSC 启动部分服务如下:
程序中的 extract_conf
函数负责解密 cscconf.bin
并提取压缩包中的内容:
decrypt_bin
函数主要是通过异或算法将 cscconf.bin
数据解密为 cscconf.tar.gz
压缩包格式:
每次取 0x420
个字节通过 xor_decrypt
函数进行加密块解异或解密,将解密后数据中的 0x400
个字节写入 tar.gz
文件:
xor_decrypt
核心代码如下,主要通过与 0x80DCB40
地址中实现存放的 64 字节逐一进行异或处理:
通过逆向该算法,使用 Python 编写出加解密算法实现代码。解密得到 cscconf.tar.gz
压缩包,解开压缩包目录如下:
在压缩包中的其中一个目录名为 service
,推测 CSC 通过该目录下的配置文件启动相关服务:
配置 Sophos vmware 网卡连网后等待一段时间,将虚拟机上的文件与原来的文件进行对比,其中有两个修改的地方,一处为 web.xml
,另一处添加了 RequestCheckFilter.class
文件:
web.xml
增加了一段配置,主要给 Sophos Java 代码添加 RequestCheckFilter
过滤器,过滤器主要检测 request 请求包中的 JSON 参数是否包含不可见字符:
检测规则中,JSON 参数的每个字符都必须是 32~127
之间,如果超出范围则会跳转到登录界面:
那么给我们的启发就是此次漏洞和 JSON 参数中的不可见字符有着直接的关系,应该是字符编码导致的认证绕过。
漏洞原理可简单理解为 Java 在使用 unicode \u0000
时,JSON 认为 key
是两个不同的 key
并没有 0
字节截断,当 Java 把含有 unicode 编码的 key
发送给后端的 Perl处理时 \u0000
产生了截断效果,使得带有 unicode 编码的 key
变为了 mode
, Perl 可以处理重复 key
的 JSON,如果重复则后面覆盖前面的值:
我们可以通过一个示例来对比不同语言处理 JSON 重复键的差异性。Java 处理带有 unicode 编码的 key
时可以正常解析:
import org.json.JSONObject;
import org.json.JSONException;
import java.io.*;
class test {
public static void main(String[] args) {
try{
System.out.println(new JSONObject("{ \"name\": \"test\", \"name\\u0000ef\": \"test2\"}"));
}catch (JSONException e){
System.out.println(e);
}
}
}
Perl 处理 Java 传递过来的零字节字符串就会产生截断效果,在处理相同 key
值的 JSON 时会取最后一个 key
对应的 value
:
#!/usr/bin/perl
use JSON;
my %rec_hash = ('a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5, 'a' => 6);
my $json = encode_json \%rec_hash;
print "$json\n";
其结果为 a
被覆盖为了最后一个 a
的值:
不同语言对 JSON 解析的差异性是此次认证绕过漏洞的核心原理。Java 接收来自用户的以下数据(其中 \u0000
后面的 ef
是为了让 JSON中 key
的 hash 排序将 716
排到 151
的后面):
{"mode":151,"mode\u0000ef":716}
通过登录 webadmin
获取登录数据包:
根据 /webconsole/Controller
及 mode=151
寻找 Java 代码中的处理逻辑,首先分析 web.xml
中路由对应的 Servlet :
主要由 CyberoamCommonServlet
进行处理,该类主要解析 mode
值,并通过 mode
进行分发:
进入 _doPost
函数继续进行分发, EventBean
从数据库中获取 mode
对应的属性,其中就包括关键属性 Requesttype
:
通过 Requesttype
将请求分为了三种:
Requesttype
为 1
,使用 CyberoamAjaxHelper
处理;Requesttype
为 2
,使用 CyberoamCustomHelper
处理;Requesttype
为其他值时,使用 generateAndSendOpcode
处理。进入 CyberoamCustomHelper
之后,通过匹配 mode
值进行分发,将会进入 WebAdminAuth
类中进行处理:
process
函数对请求中的 JSON 字段进行解析,获取 jsonObject
之后将会给 cscClient
通过 localhost:299
发送给 CSC 进程进行处理:
比较有意思的是 Sophos 通过 cscClient
返回的 Status code 判断是否登录成功,因此在 Java 代码中是看不到登录认证的完整过程的,如下图所示如果返回为 200
则会生成合法 sessionBean
:
合法 sessionBean
生成过程如下,将 session
中填充 username
、 userid
、 csrftoken
等关键信息:
因此我们只需要找到后台返回 200
的函数即可。
因为 webadmin login mode 151 的 Requesttype
为 2
,在 _send
函数最后获取返回值的时候使用 getStatusFromResponse
进行解析:
使用 eventBean
判断 Requesttype
,因为该 eventBean
在数据包刚开始处理的时候就根据 mode
值在数据库中进行搜索匹配,中间没有修改的可能性。在如下 else
分支中需要获取 JSON 结果的 status
字段:
因此在寻找可返回 200
的 mode
值时需要考虑的是要能够同时返回 status
字段,通过搜索数据库找到所有 Requesttype
为 2
的 OPCODE
,其中有 716
符合条件。注意 Perl 代码获取了 request
中的 accessaction
字段,并且需要该字段为 1
才能返回 200
。
通过前面的分析,我们很容易构造特殊的 JSON 数据包实现认证绕过。如果数据包中返回 status
的为 200
,并且 redirectionURL
路由为 index.jsp
即为认证成功,直接取 Set-Cookie
中的 JSESSIONID
进行使用:
在未登录条件下在浏览器中添加上述认证后返回的 session
,操作如下:
替换浏览器中的 JSESSIONID
,并在 url 处输入 index.jsp
回车进行跳转:
通过漏洞还能够获取 admin
操作权限,因此可以向固件中添加恶意代码,然后通过上传固件的方式实现命令执行,或者通过修改配置等方式进入底层,方法有很多种不再详细分析。
通过分析复现 CVE-2022-1040 认证绕过漏洞,学习了一种新的认证绕过思路,通过不同语言对 JSON 或者其他格式的数据处理上的差异实现变量覆盖,完成漏洞利用。
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1925/