2019 年 10 月 1 日,CVE 官方发布了编号为 CVE-2019-17059 的漏洞:Cyberoam 防火墙任意代码执行。本文对此漏洞进行分析。
漏洞的发现者在 2019 年 10 月 7 日左右发布了关于此漏洞的具体信息,但文章中隐藏了很多关键细节。
A. 数据处理流程
Cyberoam 底层使用一个修改过的 Linux 系统,整体框架设计比较复杂,包含一个 JAVA 编写的前端服务,以及 Embedded perl 编写的后端服务,前后端通过一种自定义的类 HTTP 格式数据进行通信。
A.1 前端
netstat 查看端口监听情况,发现监听 80 端口的服务启动命令为
1 | apache -d /_conf/httpd |
来到 /_conf/httpd/conf 目录下可以找到 apache 相关配置文件 httpd.conf,其内部会引用子文件:
1 | Include /cfs/web/apache/httpd.conf |
/cfs/web/apache/httpd.conf,内容如下
1 | Listen 80 |
这里看到监听外部 80 端口的服务配置,当用户访问 /corporate 接口时,请求会被转发到 http://localhost:8009/corporate,tomcat 监听此接口,启动命令:
1 | tomcat -X mx64M -X ms12M -X ss128k -X asyncgc -X noinlining -X replication:none -X compactalways -D java.io.tmpdir=/tmp-Djava.awt.headless=true -D jetty.class.path=/bin/jetty/lib/javax.servlet.jar:/bin/jetty/lib/org.mortbay.jetty.jar:/bin/jetty/webapps/corporate/properties:/bin/jetty/webapps/corporate/jar/jta26.jar:/bin/jetty/webapps/reports/properties -X bootclasspath:/bin/jamvm/share/jamvm/classes:/bin/classpath/share/classpath -D STOP.PORT=-1 -jar /bin/jetty/start.jar /bin/jetty/etc/jetty.xml |
jetty.xml 部分配置
1 |
|
这里找到 web 目录在 /bin/jetty/webapps/corporate/ 下,web.xml 部分配置
1 | <servlet-mapping> |
从配置中可以看到前端有几个可访问的接口,其中 /Controller 比较关键,它对应的类定义如下
1 | <servlet> |
反编译这个类,部分代码:
1 | // ... |
这段代码从用户请求中获取 mode 参数的值,然后进行数个判断,除特定的一些值以外,都需要进行 session 验证。
如果通过验证,则调用 CyberoamCustomHelper.process 函数,此函数逻辑复杂,简单来讲,它会进一步解析用户提交的请求,根据 mode 值的不同进行不同的处理,以无需身份验证的 458 功能为例,它会调用函数 QuarantineDownloadUtility.handleReleaseRequestFromMail,部分代码:
1 | HashMap<Object, Object> hashMap = new HashMap<Object, Object>(); |
之后会调用 cSCClient.sendWizardEvent
1 | // ... |
这里来到关键位置,send 函数内部会构造出之前提到的类 HTTP 格式数据,并转发到 127.0.0.1 299 端口。
A.2 后端
查看端口开放信息,找到监听 299 的服务是 csc 二进制文件,启动命令为
1 | csc -L 3 -c /_conf/csc/csc.conf |
在 /_conf/csc/ 目录下可以找到很多 .conf 文件,每个文件中都是一种类似 perl 的伪代码,通过 IDA 反编译分析 csc 发现程序引用了大量 perl 相关函数
经调查 csc 可能修改了原始 perl,自己实现了一套新的 perl 解释器,并在其上添加了格式解析相关逻辑。
前端所有功能发送到后端之后,都会先调用 apiInterface 接口,此接口在 perl 层面实现了鉴权、参数过滤、请求分发等操作。
我们可以利用 tcpdump 截获前后端之间的通信流量
1 | tcpdump -i lo -A port 299 |
样例请求和响应
1 | opcode getCustomerInfo csc/1.2 |
1 | csc/1.2 200 OK |
以这个请求为例,在 csc 目录下搜索字符串 getCustomerInfo 可以找到此功能的定义
1 | OPCODE getCustomerInfo { |
其余功能的调用类似,都是以类 HTTP 形式发送到 299 端口,然后 csc 找到对应 perl 函数进行调用。
A.3 mode 定义
代码中有很多 mode,每个 mode 对应 perl 中的不同函数,具体定义可以在 java Modes.class 中找到,部分定义如下
1 | public static final int ACCESS_TIME_POLICY_ADD = 1; |
B. 漏洞分析
根据披露信息,存在漏洞的功能 mode = 458,在 mode 定义中找到功能名称叫做 RELEASE_QUARANTINE_MAIL_FROM_MAIL,同时在 CyberoamCommonServlet 类中可以看到这个功能无需身份验证即可访问。
前端中对此功能处理逻辑
- CyberoamCustomHelper
1 | if (eventBean.getMode() == 458) { |
- QuarantineDownloadUtility
1 | public void handleReleaseRequestFromMail(HttpServletRequest paramHttpServletRequest, HttpServletResponse paramHttpServletResponse, SqlReader paramSqlReader) { |
- cSCClient.sendWizardEvent 发送到 299 端口
QuarantineDownloadUtility 函数中从请求中获取 release 参数,然后对其进行 base64 解码,解码后进行数个参数判断,如果全部通过则执行
1 | EventBean eventBean = EventBean.getEventByMode(363); |
这里会将 mode 重新设置为 363,从定义中得知 363 对应接口名称为 SEND_MAIL。
从 perl 伪代码中搜索能找到 send_mail 函数,部分代码:
1 | opcode send_mail{ |
代码中首先判断 release 参数是否为空,然后对其进行 base64 解码,以 $= 将字符串分割,获取 hdnDestDomain、hdnSender、hdnRecipient、hdnFilePath 四个参数并赋值到不同的变量。
接着利用正则表达式过滤 hdnRecipient 和 hdnSender 参数,验证邮箱格式是否正确,不正确直接返回 500 错误。
如果验证通过,就调用 LOG 打印这些变量的内容,然后利用 -e 判断 $file 即 $QUARANTINE_PATH.”/“.$requestData{hdnFilePath} 文件是否存在,其中 $QUARANTINE_PATH 是一个常量,定义为 /var/quarantine。
如果文件存在,就调用 mail_sender 函数
1 | OPCODE mail_sender{ |
此函数头部进行很多初始化操作,随后根据 $mailaction 值的不同执行不同逻辑,在 send_mail 函数中可以看到 传入的 $mailaction 值应该是 $MAIL_FORWARD,那么这里会执行
1 | out = EXECSH "/bin/cschelper mail_send '$fromaddress' '$fromaddresswithname' '$toEmail' '$toEmail' '$subject' '$mailbody' '$smtpserverhost' '$smtpserverport' '$mailusername' '$mailpassword' '$mailaction' '$smtpsecuritymode' '$smtpcertificate' '$certpassword' '1' '$attachmentfile'" |
EXECSH 对应的操作是 /bin/sh -c,同时这条命令中拼接了我们可控的 $smtpserverhost,存在命令注入漏洞。
C. 漏洞触发
在触发漏洞的路径上还存在几个问题
C.1 绕过文件判断
在调用 mail_sender 函数之前存在一个对 $file 是否存在的判断,其中 $file 参数是 $QUARANTINE_PATH 和用户提交的 hdnFilePath 两个变量拼接而来,也就是说只有$file 存在时,才能触发 mail_sender 功能,$QUARANTINE_PATH 变量的值为 /var/quarantine,而通常情况下攻击者无法得知此目录下有哪些文件。
为了绕过这个判断,我们可以提交 hdnFilePath=/,拼接后得到 $file = /var/quarantine/,而这里就变成了判断 /var/quarantine/ 目录是否存在,显然可以通过。
C.2 命令注入
在注入命令的时候,相关代码如下
1 | ELSE IF("$mailaction eq $MAIL_FORWARD"){ |
其中 $smtpserverport 变量是用户可控的 hdnDestDomain,但单纯使用反引号或者分号不能执行命令,需要先使用单引号将待注入的命令闭合才可以。
C.3 日志调试
perl 部分没有很好的方法可以调试,但在代码中存在很多 LOG 打印日志信息,我们可以在 /var/tslog/applog.log 文件中找到这些信息,通过调试信息辅助分析。
C.4 漏洞触发演示
- 本文作者: CataLpa
- 本文链接: https://wzt.ac.cn/2021/12/14/CVE-2019-17059/
-
版权声明:
本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。