作者:且听安全
原文链接:https://mp.weixin.qq.com/s/c0X8Ct2I2SP-H_pioMM12Q

漏洞信息

前端时间 Sophos Firewall 爆出了一个认证绕过漏洞 CVE-2022-1040 ,最近在深入分析 Sophos 服务架构的同时,完整复现了该漏洞。主要是在 User PortalWebadmin 两个接口存在认证绕过漏洞,漏洞巧妙利用了 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 服务进行交互:

图片

0x01 Apache 配置

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 65004webconsole 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

代理转发策略将 webconsoleuserportal 端口分别代理到 8009 端口的不同 URL :

ProxyPass /webconsole http://localhost:8009/webconsole
ProxyPass /userportal http://localhost:8009/userportal

0x02 Jetty 配置

Jetty 配置文件为 /usr/share/jetty/start.ini,开启了本地服务的 8009 端口:

图片

Jetty 的启动参数在 /usr/bin/jetty 脚本中配置,如果要修改可直接修改该文件最后 Java 执行部分。

0x03 CSC 配置

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 参数中的不可见字符有着直接的关系,应该是字符编码导致的认证绕过。

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/Controllermode=151 寻找 Java 代码中的处理逻辑,首先分析 web.xml 中路由对应的 Servlet :

图片

图片

主要由 CyberoamCommonServlet 进行处理,该类主要解析 mode 值,并通过 mode 进行分发:

图片

进入 _doPost 函数继续进行分发, EventBean 从数据库中获取 mode 对应的属性,其中就包括关键属性 Requesttype

图片

通过 Requesttype 将请求分为了三种:

图片

  • Requesttype1 ,使用 CyberoamAjaxHelper 处理;
  • Requesttype2 ,使用 CyberoamCustomHelper 处理;
  • Requesttype 为其他值时,使用 generateAndSendOpcode 处理。

进入 CyberoamCustomHelper 之后,通过匹配 mode 值进行分发,将会进入 WebAdminAuth 类中进行处理:

图片

process 函数对请求中的 JSON 字段进行解析,获取 jsonObject 之后将会给 cscClient 通过 localhost:299 发送给 CSC 进程进行处理:

图片

比较有意思的是 Sophos 通过 cscClient 返回的 Status code 判断是否登录成功,因此在 Java 代码中是看不到登录认证的完整过程的,如下图所示如果返回为 200 则会生成合法 sessionBean

图片

合法 sessionBean 生成过程如下,将 session 中填充 usernameuseridcsrftoken 等关键信息:

图片

因此我们只需要找到后台返回 200 的函数即可。

CSC Perl API 分析

因为 webadmin login mode 151 的 Requesttype2 ,在 _send 函数最后获取返回值的时候使用 getStatusFromResponse 进行解析:

图片

使用 eventBean 判断 Requesttype ,因为该 eventBean 在数据包刚开始处理的时候就根据 mode 值在数据库中进行搜索匹配,中间没有修改的可能性。在如下 else 分支中需要获取 JSON 结果的 status 字段:

图片

因此在寻找可返回 200mode 值时需要考虑的是要能够同时返回 status 字段,通过搜索数据库找到所有 Requesttype2OPCODE ,其中有 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 或者其他格式的数据处理上的差异实现变量覆盖,完成漏洞利用。


Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1925/