前言

刚开始实战代码审计,去github上找了几个100+ stars 的项目练手.

本来看着大佬们找sql注入,xss这种漏洞的文章挺简单的,以为自己上手随便搞搞也能弄几个.

现实很残酷,当代码量到几万行,甚至十万行级别,静态审计一下就把自己搞晕了,动态调试又问题一堆.并非想象中这么容易.

没有成果给自己一个正向的激励,很难坚持代码审计这样枯燥的事情.所以写了这个思路来帮助和我一样菜的菜鸡找到自己的第一个漏洞.

各大论坛关于代码审计的文章不少,而方法论的内网罕见.这里我介绍一种比较简单常见的方法

当然我本身也是个菜鸡,这个方法也是其他地方看到的

正文

0x01 代码审计中的信息收集

一个cms代码量确实不少,通读代码耗时长,效果也不一定好.而一个功能点如果之前出过漏洞,特别是多次出现漏洞的地方,证明开发者对这个漏洞的理解不充分,很容易再次绕过补丁.这样,一整个CMS的代码审计就可以降维到一道ctf题目.特别是对于经常参加ctf的各位大佬来说,这样的代码审计更加简单休闲.我记得之前也有机构统计过,出过漏洞的地方更容易再次出现漏洞,普通CMS的开发者通常不是专业的安全人员,也不一定有专业的安全专家协助修复,再次出现漏洞的可能性就更大了.

我以github上的一个百星icms为例.

icms github链接: https://github.com/idreamsoft/iCMS \

在issue中搜索SSRF https://github.com/idreamsoft/iCMS/issues?utf8=%E2%9C%93&q=is%3Aissue+ssrf

在cve列表中查找,应该对应的就是这三个cve了

可以看到这个功能点已经出现了三次的绕过与过滤.

大致了解下这个功能点,是一个自动更新文章的爬虫,多处都可以控制url参数.

点开issue查看具体信息,我们从最早出现漏洞的版本看起.

通过查看具体的commits,可以找到开发者修复漏洞的思路.这给我们代码审计带来很大的便利.

CVE-2018-14514 漏洞分析

commit: https://github.com/idreamsoft/iCMS/issues/29

提交者详细描述了漏洞信息,只指出了一个点,但根据作者修复的commit,有两处都存在SSRF漏洞.

SSRF:

public static function postUrl($url, $data) {
        is_array($data) && $data = http_build_query($data);
        $options = array(
            CURLOPT_URL    => $url,
            ...
        );

        $ch = curl_init();
        curl_setopt_array($ch,$options);
        $responses = curl_exec($ch);
        curl_close ($ch);
        return $responses;
    }

icms7.0.9\app\spider\spider_tools.class.php 604行,关键代码:

public static function remote($url, $_count = 0) {
    $url = str_replace('&', '&', $url);
    if(empty(spider::$referer)){
        $uri = parse_url($url);
        spider::$referer = $uri['scheme'] . '://' . $uri['host'];
    }
    self::$curl_info = array();
    $options = array(
        CURLOPT_URL                  => $url,
        ...
    );
    spider::$cookie && $options[CURLOPT_COOKIE] = spider::$cookie;
    if(spider::$curl_proxy){
        $proxy   = self::proxy_test();
        $proxy && $options = iHttp::proxy($options,$proxy);
    }
    if(spider::$PROXY_URL){
        $options[CURLOPT_URL] = spider::$PROXY_URL.urlencode($url);
    }
    $ch = curl_init();
    curl_setopt_array($ch,$options);
    $responses = curl_exec($ch);
    ...
}

两处都是因为使用了curl,且无安全措施,只需要url参数可控即可进行SSRF攻击.

可以看到icms7.0.9版本没有做任何的验证,并且可以使用任意协议访问任意ip与端口.因此如果有redis或无密码的mysql或者一些其他容易被攻击的服务,可以getshell.因为这里重点不是通过SSRF如何getshell ,因此不做getshell的验证.

我们找一处漏洞点测试,有很多处都调用了remote函数,全局搜索即可,我们找一个能即时回显的点测试.

payload:http://ip/admincp.php?app=spider&do=testdata&url=dict://127.0.0.1:8000&rid=2&pid=0&title=m09ic

监听端口观察是否有数据过来.

很明显收到了.

CVE-2018-14514 补丁分析

我们再来看看作者是如何修复的,commit: https://github.com/idreamsoft/iCMS/commit/64bb0bdf77febbd6ac0ccb6658ee1ddc71530bb1

public static function remote($url, $_count = 0) {
    if(!iHttp::is_url($url,true)){
        if (spider::$dataTest || spider::$ruleTest) {
            echo "<b>{$url} 请求错误:非正常URL格式,因安全问题只允许抓取 http:// 或 https:// 开头的链接</b>";
        }
        return false;
    }

作者添加了一个判断函数

public static function is_url($url,$strict=false) {
    $url = trim($url);
    if($strict){
    return (stripos($url, 'http://') === 0 || stripos($url, 'https://') === 0);
    }

    if (stripos($url, 'http://') === false && stripos($url, 'https://') === false) {
    return false;
    } else {
    return true;
    }
}

先判断url是否以http://开头,才开始解析,这样就限制了危险的协议,减轻了危害程度,大部分情况很难getshell.但是SSRF漏洞依然存在.

可以发现,作者对SSRF漏洞的认识并不到位,认为不能getshell就可以了.但是实际上,用HTTP协议也并非完全不可能getshell,内网有可能存在一些可以被GET请求getshell的服务,比如thinkphp的几个RCE,就算不能RCE,SSRF也可以直接被用来进行内网信息收集,同样是不可忽视的漏洞.

CVE-2018-14858 补丁分析

显然只允许http与https开头的url访问,SSRF依然存在,于是在icms7.0.11版本,又有人提交了SSRF漏洞,并获得了一个CVE编号.漏洞成因与上一个漏洞一致,作者的过滤措施虽然缓解了该漏洞的危害,但是漏洞依然存在.下面是作者在issue中的回复:

提交者除了提交漏洞,还简单说明了几种常见的ssrf绕过手法,比如不同格式的ip地址.

这是作者在issue33下的回复.

I know this question, but if the IP format is banned, the website using the IP format will not be collected. Although it is not used a lot, it will still be encountered. There is no better way to think about it now.

然后过了几天,作者意识到了这样并不算修复了SSRF漏洞,再次commit了一个补丁.

具体更新内容: https://github.com/idreamsoft/iCMS/commit/62de04e57a67f2690dbf88b7d381af61a0969ef3

添加了过滤代码,关键代码如下:

public static function remote($url, $_count = 0) {
    if(!iHttp::is_url($url,true)){
        $parsed = parse_url($url);//解析url
        $validate_ip = true;
        preg_match('/\d+/', $parsed['host']) && $parsed['host'] = long2ip($parsed['host']);//获取host部分,如果是十进制或其他进制的ip地址,转化成标准的ip地址
        if(preg_match('/\d+\.\d+\.\d+\.\d+/', $parsed['host'])){
            $validate_ip = filter_var($parsed['host'], FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE);//匹配正确的ip格式,过滤非法ip地址字符与内外地址
        }
        if(!in_array($parsed['scheme'],array('http','https')) || !$validate_ip|| strtolower($parsed['host'])=='localhost'){
            if (spider::$dataTest || spider::$ruleTest) {
                echo "<b>{$url} 请求错误:非正常URL格式,因安全问题只允许抓取 http:// 或 https:// 开头的链接</b>";
                echo "<b>{$url} 请求错误:非正常URL格式,因安全问题只允许抓取 http:// 或 https:// 开头的链接或私有IP地址</b>";
            }
            return false;
        }
    }
        ...

}

可以看到,这次添加了检查ip地址的格式,以及是否是内网ip.

以普通开发者的角度思考,很多情况都是哪里出了问题就修哪里,什么东西能绕过就过滤什么.也很难要求他们完全了解安全漏洞,因此也导致了修复再次被绕过.

CVE-2018-15895补丁分析

与上个漏洞提交者是同一个人. https://github.com/idreamsoft/iCMS/issues/40

然而,普通开发人员通常是哪里有问题就去解决哪里的问题,并不一定能对某个漏洞有深入的认识,更不用说了解全部攻击与绕过手段,但是只要漏了一种,修复补丁就等于完全没有.

我们都知道,SSRF的常用绕过手法还有302重定向与DNS重绑定.漏洞提交者也演示了这两种方式.具体POC可以看issue内容.

关键代码如下:

public static function safe_url($url) {
        $parsed = parse_url($url);
        $validate_ip = true;

        if($parsed['port'] && is_array(self::$safe_port) && !in_array($parsed['port'],self::$safe_port)){
            if (spider::$dataTest || spider::$ruleTest) {
                echo "<b>请求错误:非正常端口,因安全问题只允许抓取80,443端口的链接,如有特殊需求请自行修改程序</b>".PHP_EOL;
            }
            return false;
        }else{
            preg_match('/^\d+$/', $parsed['host']) && $parsed['host'] = long2ip($parsed['host']);
            $long = ip2long($parsed['host']);
            if($long===false){
                $ip = null;
                if(self::$safe_url){
                    @putenv('RES_OPTIONS=retrans:1 retry:1 timeout:1 attempts:1');
                    $ip   = gethostbyname($parsed['host']);
                    $long = ip2long($ip);
                    $long===false && $ip = null;
                    @putenv('RES_OPTIONS');
                }
            }else{
                $ip = $parsed['host'];
            }
            $ip && $validate_ip = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE);
        }

        if(!in_array($parsed['scheme'],array('http','https')) || !$validate_ip){
            if (spider::$dataTest || spider::$ruleTest) {
                echo "<b>{$url} 请求错误:非正常URL格式,因安全问题只允许抓取 http:// 或 https:// 开头的链接或公有IP地址</b>".PHP_EOL;
            }
            return false;
        }else{
            return $url;
        }
    }

可以看到,使用了第17行使用了gethostbyname 确定parse_url解析后的host部分,来防护DNS rebinding 攻击.

并且在curl的options中,注释了// CURLOPT_FOLLOWLOCATION => 1,// 使用自动跳转,来防护302重定向绕过.

经常打ctf的小伙伴可能就会注意到了,攻击的思路可以针对parse_url的解析问题.历代parse_url存在不少方式缺陷,比如scheme,host,port,path等均有过绕过的记录.而这些细节,是开发者很难注意到的.如果想要再次绕过,这里就是个很好的突破点.

新的绕过

光黑名单和检查host真实ip来说,基本上是万无一失.但是作者万万没想到,来自php自身的背后一刀.在2017年blackhat上orange师傅演讲的 A New Era of SSRF 中,有一个新的攻击方式,利用php中的parse_url函数和libcurl对url的解析差异,导致了对host的过滤失效,成功绕过.

从orange师傅的ppt中偷一张图来解释.

php-curl拓展解析url的host在第二个@之后,而parse_url则是最后一个@之后.

因此我们可以使用如下payload绕过:

http://ip/admincp.php?app=spider_project&do=test&url=http://m09ic@127.0.0.1:81@baidu.com/&rid=2&pid=1&title=

可以看到,同时绕过了port和host的限制,访问到了只对本地开放的81端口的phpinfo内容.成功绕过过滤实现SSRF.

这里有一个小坑,在较新版本的php-curl中,已经修复了多个@的解析问题,使用多个@会报错,不知道为啥不是调整到与parse_url一致,这种修复显然影响了可用性.

该漏洞也不单是cms的问题,也有curl的问题.不管所使用的所有开源组件是不是安全的,在常见漏洞上cms中再加一层过滤是必要的.

大多数linux发行版并没有使用最新版本的curl.可以在 https://curl.haxx.se/download.html 这里查询linux发行版与curl版本的对应关系,应该少有公司会实时更新操作系统版本,只要不是最新版本的操作系统,基本都存在该漏洞.

我只测试了ubuntu,在ubuntu16.04及以下均可以使用该方式绕过.而在ubuntu18.04中,已经不再可以.exec_curl函数执行会直接返回false.

ubuntu16.04的curl版本是:

# curl -V
curl 7.47.0 (x86_64-pc-linux-gnu) libcurl/7.47.0 GnuTLS/3.4.10 zlib/1.2.8 libidn/1.32 librtmp/2.3
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtmp rtsp smb smbs smtp smtps telnet tftp 
Features: AsynchDNS IDN IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz TLS-SRP UnixSockets

已经提交了issue,坐等作者的修复,期待是否还有被绕过的可能:D

最后

(面向github代码审计)

一个开发人员很难有精力去了解一个攻击方式的方方面面,也很难让开发者紧跟攻击手法的趋势.在刚才的例子看到,虽然开发者积极的解决漏洞,但是并不能有效缓解漏洞,总有普通开发者不知道的方式再次绕过.

总得来说,初尝代码审计时,可以多翻翻issue中多次出现漏洞的点,这种地方再次出现漏洞的几率相对来说较高.

另外,这个漏洞在利用要进入后台,又过滤了各种敏感协议,实际上危害并不大,仅仅用来学习代码审计的思路以及常见SSRF的绕过与防护方式.

点击收藏 | 1 关注 | 1
登录 后跟帖