CVE-2019-17059:Cyberoam SSL VPN的RCE漏洞
我们一直致力于和大量安全研究人员通力合作,挖掘出各大SSL VPN和防火墙的严重漏洞,Cyberoam、Fortigate和Cisco vpn等都是我们的研究对象。而本文是关于影响了Cyberoam SSL VPN(也可称为CyberoamOS)的远程命令执行漏洞的一些基本说明。
这个Cyberoam漏洞被标记为CVE-2019-17059
,它是一个高危漏洞,攻击者可利用它在未经授权的情况下直接控制Cyberoam设备。最重要的是,攻击者的访问权限是root
,这代表给攻击者可以完全控制Cyberoam设备。
由于在大多数网络环境中,Cyberoam设备都被用作防火墙或SSL VPN,一旦攻击者完全控制设备,就可对目标内网进行大面积攻击。而且在一般情况下,Cyberoam设备通常在安全“白名单”中,这就帮助攻击者更好地绕过其他安全防护。
根据Shodan的数据,全世界有超过96000个位于互联网的Cyberoam设备。这些设备大多位于企业、大学和一些世界知名银行地网络中,一旦沦陷,可能会造成大量经济损失。
与Sophos安全团队合作是一件非常愉快的事情,因为他们在接到报告后很快就意识到了严重性,并立刻发布了补丁。
CyberoamOS远程命令执行
CyberoamOS是一种基于linux的改进版操作系统,主要用于Cyberoam设备。该操作系统具有基于web的配置接口和一个SSL VPN入口。
网页界面主要分为两部分:
用Java编写的前端
使用C和Perl编写的后端
我们在此不会深入研究前端或后端代码的内部机制,而是简要讨论如何触发这个漏洞。
配置接口和SSL VPN入口都有一个处理主要操作的servlet。这些操作是通过参数mode
定义的。
其中大多数操作是需要身份验证的,但有一些操作可以在不需要身份验证的情况下进行(比如登录操作)。
我们发现的漏洞位于电子邮件防病毒/反垃圾邮件
模块。此模块的请求代码是458。
需要注意的是,操作码会对应到Cyberoam设备数据库(指内部数据库Postgres)中的数据。因此通过查找458,我们可以找到对应操作码的名称。
下面是数据库初始化SQL脚本中的一行,展示了458对应的操作名称:
insert into tblcrevent(opcode,desc ription,mode,requesttype)
values('RELEASEQUARANTINEMAILFROMMAIL','RELEASE QUARANTINE MAIL FROM MAIL','458',2);
而执行操作的函数存储在/_conf/csc/cscconf/
目录中。我们不会透露存在缺陷的函数的全部代码,但是我们将提供一些代码片段来展示漏洞的位置以及它是如何触发的。
处理操作码458的Java前端代码:
if ((jsonob ject.getString("hdnSender").equals("") ||
validateEmail(jsonob ject.getString("hdnSender"))) &&
validateEmail(jsonob ject.getString("hdnRecipient")) &&
isSafeFilePath(jsonob ject.getString("hdnFilePath")) && b) {
httpServletResponse.setContentType("text/html");
CyberoamLogger.debug("Antivirus/AntiSpam", "CSC Constant value " +
CSCConstants.isCCC);
正如上面的代码所展示的,它检查了几个参数的有效性。如果确认有效,则会发生以下情况:
final EventBean eventByMode = EventBean.getEventByMode(363);
...redacted.
final int sendWizardEvent = cscClient.sendWizardEvent(eventByMode, hashMap, sqlReader);
此时我们有一个新的事件代码(363),它将被发送到后端。而漏洞就存在后端处理这个事件的代码中。
该操作码对应名称为sendmail
,为了避免直接暴露出漏洞,我们只展示部分代码。
send_mail
的处理程序。
...redacted...
<code>$param = $request->{release};</code>
param = DLOPEN(ba se64_decode,param)
LOG applog " Decode values :: $param \n"
<code>%requestData = split(/[&=]/, $param);
$mailServerHost = $requestData{hdnDestDomain};
$mailFrom = $requestData{hdnSender};
$mailTo = $requestData{hdnRecipient};
$file = $QUARANTINE_PATH."/".$requestData{hdnFilePath};
$mailfile=$requestData{hdnFilePath};
$validate_email="false";
my $email_regex='^([\.]?[_\-\!\#\{\}\$\%\^\&\*\+\=\|\?\'\\\\\\/a-zA-Z0-9])*@([a-zA-Z0-9]([-]?[a-zA-Z0-9]+)*\.)+([a-zA-Z0-9]{0,6})$';
if($requestData{hdnRecipient} =~ /$email_regex/ && ((defined $requestData{hdnSender} && $requestData{hdnSender} eq '') || $requestData{hdnSender} =~ /$email_regex/) && index($requestData{hdnFilePath},'../') == -1){
$validate_email="true";
}
....redacted....
正如我们从上面代码看到的,伪perl代码向我们展示了后端如何接收输入($requestData),以及它是如何尝试验证所接受的一些参数的。
验证后,如果我们的参数有效,则执行以下代码:
%mailreq=("mailaction"=>"$MAIL_FORWARD","subject"=>"$strSubject","toEmail"=>"$mailTo","attachmentfile"=>"$file","smtpserverhost"=>"$mailServerHost","fromaddress"=>"$mailFrom");
</code>
out = OPCODE mail_sender json %mailreq
以上代码将把变量mailreq
设置为接受的值,并调用mail_sender
函数。我们将看到这其中涉及的操作,以及RCE到底在哪里触发:
<code>
#mailaction 0=mail_with_var,1=mail_forward,2=mail_attachment
$mailaction=$request->{mailaction};
$subject=$request->{subject};
$mailbody='';
$attachmentfile=$request->{attachmentfile};
$toEmail=$request->{toEmail};
</code>
#mail body
IF("defined $request->{mailbody} && '' ne $request->{mailbody}"){
<code>$mailbody=$request->{mailbody};</code>
}
#SMTP server host
IF("defined $request->{smtpserverhost} && '' ne $request->{smtpserverhost}"){
<code>$smtpserverhost=$request->{smtpserverhost};</code>
}ELSE{
result = QUERY "select servicevalue from tblclientservices where servicekey='MailServer'"
IF("defined $result->{output}->{servicevalue}[0] && '' ne $result->{output}->{servicevalue}[0]"){
<code>$smtpserverhost=$result->{output}->{servicevalue}[0];</code>
}ELSE{
<code>$smtpserverhost="127.0.0.1";</code>
}
}
#SMTP server port
IF("defined $request->{smtpserverport} && '' ne $request->{smtpserverport}"){
<code>$smtpserverport=$request->{smtpserverport};</code>
}ELSE{
result = QUERY "select servicevalue from tblclientservices where servicekey='MailServerPort'"
IF("defined $result->{output}->{servicevalue}[0] && '' ne $result->{output}->{servicevalue}[0]"){
<code>$smtpserverport=$result->{output}->{servicevalue}[0];</code>
}ELSE{
<code>$smtpserverport="25";</code>
}
}
#SMTP auth flag
<code>$smtpauthflag="0";</code>
IF("defined $request->{smtpauthflag} && '' ne $request->{smtpauthflag}"){
<code>$smtpauthflag=$request->{smtpauthflag};</code>
}ELSE{
result = QUERY "select servicevalue from tblclientservices where servicekey='SMTPAuthenticationFlag'"
IF("defined $result->{output}->{servicevalue}[0] && '' ne $result->{output}->{servicevalue}[0]"){
<code>$smtpauthflag=$result->{output}->{servicevalue}[0];</code>
}
}
IF("$smtpauthflag == 1"){
IF("defined $request->{mailusername} && '' ne $request->{mailusername}"){
<code>
$mailusername=$request->{mailusername};
$mailpassword=$request->{mailpassword};
</code>
}ELSE{
result = QUERY "select servicevalue from tblclientservices where servicekey = 'MailServerUsername'"
<code>$mailusername = $result->{output}->{servicevalue}[0];</code>
result = QUERY "select servicevalue from tblclientservices where servicekey = 'MailServerPassword'"
<code>$mailpassword = $result->{output}->{servicevalue}[0];</code>
}
}ELSE{
<code>
$mailusername = "";
$mailpassword = "";
</code>
}
IF("defined $request->{fromaddress} && '' ne $request->{fromaddress}"){
<code>$fromaddress=$request->{fromaddress};</code>
}ELSE{
result = QUERY "select servicevalue from tblclientservices where servicekey = 'FromAddress'"
<code>$fromaddress = $result->{output}->{servicevalue}[0];</code>
}
#Security Mode
IF("defined $request->{smtpsecurity} && '' ne $request->{smtpsecurity}"){
<code>$smtpsecurity=$request->{smtpsecurity};</code>
}ELSE{
result = QUERY "select servicevalue from tblclientservices where servicekey = 'smtpsecurity'"
<code>$smtpsecurity = $result->{output}->{servicevalue}[0];</code>
}
<code>$smtpsecuritymode=0;</code>
IF("$smtpsecurity eq 'STARTTLS'"){
<code>$smtpsecuritymode=1;</code>
}ELSE IF("$smtpsecurity eq 'SSL/TLS'"){
<code>$smtpsecuritymode=2;</code>
}
#SMTP Certificate
<code>
$smtpcertificate = '';
$certpassword='';
</code>
IF("$smtpsecuritymode!=0"){
IF("defined $request->{smtpcertificate} && '' ne $request->{smtpcertificate}"){
result = QUERY "select certname,password from tblvpncertificate where certid=$request->{smtpcertificate}"
}ELSE{
result = QUERY "select certname,password from tblvpncertificate where certid=(select servicevalue::int from tblclientservices where servicekey = 'smtpcertificate')"
}
<code>
$smtpcertificate = $result->{output}->{certname}[0];
$certpassword=$result->{output}->{password}[0];
</code>
}
#From Address with Name
IF("defined $request->{fromaddresswithname} && '' ne $request->{fromaddresswithname}"){
<code>$fromaddresswithname=$request->{fromaddresswithname};</code>
}ELSE{
<code>$fromaddresswithname = $OEMNAME . " <" . $fromaddress . ">";</code>
}
上面的代码在开始运行时会做一件所有代码都会执行的操作,初始化变量(如果没有指定一些变量,则直接取自设备)。
在分配变量之后,将执行以下代码。
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 “ARGUMENTS”
。通过控制所需执行的“变量”,我们就可以在未经身份验证的情况下轻松实现远程命令执行。
我们将在几个月内发布一份完整的报告和PoC,并给出更详细的说明。
本文由白帽汇整理并翻译,不代表白帽汇任何观点和立场
来源:https://thebestvpn.com/cyberoam-preauth-rce/?=cve-2019-17059-flaw
最新评论