如何将代码审计变成CTF---面向github代码审计

2020-02-25 约 524 字 预计阅读 3 分钟

声明:本文 【如何将代码审计变成CTF—面向github代码审计】 由作者 M09Ic 于 2020-02-25 08:55:03 首发 先知社区 曾经 浏览数 287 次

感谢 M09Ic 的辛苦付出!

前言

刚开始实战代码审计,去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代码审计)

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

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

关键词:[‘安全技术’, ‘漏洞分析’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now