浅谈php代码审计

代码审计,顾名思义就是检查源代码中的安全缺陷。通过自动化工具或着人工审查的方式,对程序源代码逐条进行检查和分析,发现源码缺陷引发的安全漏洞,需要提供代码修订措施和修复建议。

以下为php代码审计的一般方法:

1。逐条读代码  

2。敏感函数回溯(使用工具审计)

3。定向功能分析

下面进行逐一介绍:

1 逐条读代码:通过对整个项目的代码进行阅读,从而发现问题,这种方法是最全面的,但也是最麻烦的,最容易出错。

而且大型的程序源码,代码巨大,这种方法会相当耗费时间,所以一般是企业针对自身产品进行审计,不得不说,这种方法很有用,能够了解到整个应用的程序架构及业务逻辑,以此可以挖掘到更多的高质量的漏洞,尤其是容易忽视的逻辑漏洞,对于小型程序源码,也可以使用这种方法进行审计。

2 敏感函数回溯(使用工具审计):大多数的漏洞主要是php的函数的使用不当造成的,只要找到这些使用不当的函数,就可以快速的发现想要挖掘的漏洞。

这种方法相对比较快速和高效,也可以使用工具进行审计,一般可以用Seay源代码审计系统。原理是利用正则表达式,匹配一些危险的函数、关键函数、敏感关键字,然后得到这些函数,判断代码的上下文,找到参数的源头所在。

3。定向功能分析:有些程序代码量确实不少,通读代码耗时长,效果也不一定好。所以该方法主要是根据程序的业务逻辑和业务功能进行审计的,首先大概浏览网站的页面,比如有上传功能,有浏览功能,可能猜测到这个程序有上传漏洞、XSS漏洞等,可以大概的推测它有哪些漏洞,然后再针对猜测的结果,进行定向分析。 常见的功能漏洞:

程序初始安装漏洞

站点信息泄露

文件上传管理

登录认证、权限管理漏洞

数据库备份漏洞

验证码漏洞等

 

有以下总结:

首先,不管是什么程序都要把握大局,了解整体的架构。

其次,根据定向功能发对每一项功能进行审计,可以根据网站的架构使用不同的方法进行分析。

最后,可以将敏感函数进行回溯,找到初始定义的函数,发现漏洞的起源地。

 

举个栗子,拿github上一个cms来说:https://github.com/idreamsoft/iCMS

下载解压缩后,一般来说,大致为以下的目录结构,

 

admin:后台管理目录

install:网站的安装目录,其中的install.sql为数据库的结构信息

sys:这个目录里面一般存放着配置信息文件和公共函数库,分别为config.phplib.php
user
:这里面记录着用户的一些操作,如用户注册等

index.php:一般为网页的首页文件,也是我们审计的开始

 

这里入口为http://ip/icms-7.0/admincp.php

账号密码为导进去数据库文件的定义好了数据,密码采用32MD5加密,若忘记初始密码,则可以重置。

登录进去。

可以在https://github.com/idreamsoft/iCMS/issues?utf8=%E2%9C%93&q=xss搜索关于xss的历史相关漏洞及修复。点开有相应的漏洞原理及修复。

如果一个功能点之前出过很多漏洞,很可能再次绕过补丁。以SSRF为例,作者总共做了三次修复。我们从最早版本修复开始看起,可以找到开发者修复漏洞的思路,这也给我们代码审计带来一点启示。

首先使用了curl,而且未做任何安全措施。只需要url参数可控即可进行SSRF攻击。

public static function postUrl($url, $data) {
            。。。
        $ch = curl_init();
        curl_setopt_array($ch,$options);
        $responses = curl_exec($ch);
        curl_close ($ch);
        return $responses;
    }

随后作者做了一个判断函数,先判断url是协议否以http://开头,才开始解析,这样就限制了危险的协议,虽然很难getshell。但是SSRF漏洞依然存在。

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;
    }
}

实际上,可以发现,用HTTP协议也并非完全不可能getshell,内网有可能存在一些可以被GET请求getshell的服务,SSRF也可以直接被用来进行内网信息收集,同样是不可忽视的漏洞。作者的过滤措施虽然缓解了该漏洞的危害,但是漏洞依然存在。

添加了过滤代码,如下:

public static function remote($url, $_count = 0) {
        if(!iHttp::is_url($url,true)){
        $parsed = parse_url($url);
        $validate_ip = true;
        preg_match('/\d+/', $parsed['host']) && $parsed['host'] = long2ip($parsed['host']);
        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);
        }
        if(!in_array($parsed['scheme'],array('http','https')) || !$validate_ip|| strtolower($parsed['host'])=='localhost'){
            if (spider::$dataTest || spider::$ruleTest) {
                echo "{$url} 请求错误:非正常URL格式,因安全问题只允许抓取 http:// 或 https:// 开头的链接";
                echo "{$url} 请求错误:非正常URL格式,因安全问题只允许抓取 http:// 或 https:// 开头的链接或私有IP地址";
            }
            return false;
        }
        $url = str_replace('&', '&', $url);
        if(empty(spider::$referer)){
            $uri = parse_url($url);
            spider::$referer = $uri['scheme'] 。 '://' 。 $uri['host'];
            spider::$referer = $parsed['scheme'] 。 '://' 。 $parsed['host'];
        }
        self::$curl_info = array();
        $options = array(
@@ -665,7 +670,7 @@ public static function remote($url, $_count = 0) {
                           if(empty($newurl)) return false;
 
                       if(!strstr($newurl,'http://')){
                               $host   = $uri['scheme']。'://'。$uri['host'];
                               $host   = $parsed['scheme']。'://'。$parsed['host'];
                              $newurl = $host。'/'。$newurl;
                       }
                }      

然而SSRF的常用绕过手法还有302重定向与DNS重新绑定攻击。

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

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 "请求错误:非正常端口,因安全问题只允许抓取80,443端口的链接,如有特殊需求请自行修改程序"。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 "{$url} 请求错误:非正常URL格式,因安全问题只允许抓取 http:// 或 https:// 开头的链接或公有IP地址"。PHP_EOL;
            }
            return false;
        }else{
            return $url;
        }
    }

这里第17行使用了gethostbyname 确定parse_url解析后的host部分,来防护DNS rebinding 攻击。

之前看过一句话:“如果你想搞懂一个漏洞,比较好的方法是:你可以自己先制造出这个漏洞(用代码编写),然后再利用它,最后再修复它”。

代码审计能力和开发能力,将会是未来做安全越来越重要的一项技能。

浅谈php代码审计

 

代码审计,顾名思义就是检查源代码中的安全缺陷。通过自动化工具或着人工审查的方式,对程序源代码逐条进行检查和分析,发现源码缺陷引发的安全漏洞,需要提供代码修订措施和修复建议。

以下为php代码审计的一般方法:

1。逐条读代码  

2。敏感函数回溯(使用工具审计)

3。定向功能分析

下面进行逐一介绍:

1 逐条读代码:通过对整个项目的代码进行阅读,从而发现问题,这种方法是最全面的,但也是最麻烦的,最容易出错。

而且大型的程序源码,代码巨大,这种方法会相当耗费时间,所以一般是企业针对自身产品进行审计,不得不说,这种方法很有用,能够了解到整个应用的程序架构及业务逻辑,以此可以挖掘到更多的高质量的漏洞,尤其是容易忽视的逻辑漏洞,对于小型程序源码,也可以使用这种方法进行审计。

2 敏感函数回溯(使用工具审计):大多数的漏洞主要是php的函数的使用不当造成的,只要找到这些使用不当的函数,就可以快速的发现想要挖掘的漏洞。

这种方法相对比较快速和高效,也可以使用工具进行审计,一般可以用Seay源代码审计系统。原理是利用正则表达式,匹配一些危险的函数、关键函数、敏感关键字,然后得到这些函数,判断代码的上下文,找到参数的源头所在。

3。定向功能分析:有些程序代码量确实不少,通读代码耗时长,效果也不一定好。所以该方法主要是根据程序的业务逻辑和业务功能进行审计的,首先大概浏览网站的页面,比如有上传功能,有浏览功能,可能猜测到这个程序有上传漏洞、XSS漏洞等,可以大概的推测它有哪些漏洞,然后再针对猜测的结果,进行定向分析。 常见的功能漏洞:

程序初始安装漏洞

站点信息泄露

文件上传管理

登录认证、权限管理漏洞

数据库备份漏洞

验证码漏洞等

 

有以下总结:

首先,不管是什么程序都要把握大局,了解整体的架构。

其次,根据定向功能发对每一项功能进行审计,可以根据网站的架构使用不同的方法进行分析。

最后,可以将敏感函数进行回溯,找到初始定义的函数,发现漏洞的起源地。

 

举个栗子,拿github上一个cms来说:https://github.com/idreamsoft/iCMS

下载解压缩后,一般来说,大致为以下的目录结构,

 

admin:后台管理目录

install:网站的安装目录,其中的install.sql为数据库的结构信息

sys:这个目录里面一般存放着配置信息文件和公共函数库,分别为config.phplib.php
user
:这里面记录着用户的一些操作,如用户注册等

index.php:一般为网页的首页文件,也是我们审计的开始

 

这里入口为http://ip/icms-7.0/admincp.php

账号密码为导进去数据库文件的定义好了数据,密码采用32MD5加密,若忘记初始密码,则可以重置

登录进去。

可以在https://github.com/idreamsoft/iCMS/issues?utf8=%E2%9C%93&q=xss搜索关于xss的历史相关漏洞及修复。点开有相应的漏洞原理及修复。

如果一个功能点之前出过很多漏洞,很可能再次绕过补丁。以SSRF为例,作者总共做了三次修复。我们从最早版本修复开始看起,可以找到开发者修复漏洞的思路,这也给我们代码审计带来一点启示。

首先使用了curl,而且未做任何安全措施。只需要url参数可控即可进行SSRF攻击。

public static function postUrl($url, $data) {
            。。。
        $ch = curl_init();
        curl_setopt_array($ch,$options);
        $responses = curl_exec($ch);
        curl_close ($ch);
        return $responses;
    }

随后作者做了一个判断函数,先判断url是协议否以http://开头,才开始解析,这样就限制了危险的协议,虽然很难getshell。但是SSRF漏洞依然存在。

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;
    }
}

实际上,可以发现,用HTTP协议也并非完全不可能getshell,内网有可能存在一些可以被GET请求getshell的服务,SSRF也可以直接被用来进行内网信息收集,同样是不可忽视的漏洞。作者的过滤措施虽然缓解了该漏洞的危害,但是漏洞依然存在。

添加了过滤代码,如下:

public static function remote($url, $_count = 0) {
        if(!iHttp::is_url($url,true)){
        $parsed = parse_url($url);
        $validate_ip = true;
        preg_match('/\d+/', $parsed['host']) && $parsed['host'] = long2ip($parsed['host']);
        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);
        }
        if(!in_array($parsed['scheme'],array('http','https')) || !$validate_ip|| strtolower($parsed['host'])=='localhost'){
            if (spider::$dataTest || spider::$ruleTest) {
                echo "{$url} 请求错误:非正常URL格式,因安全问题只允许抓取 http:// 或 https:// 开头的链接";
                echo "{$url} 请求错误:非正常URL格式,因安全问题只允许抓取 http:// 或 https:// 开头的链接或私有IP地址";
            }
            return false;
        }
        $url = str_replace('&', '&', $url);
        if(empty(spider::$referer)){
            $uri = parse_url($url);
            spider::$referer = $uri['scheme'] 。 '://' 。 $uri['host'];
            spider::$referer = $parsed['scheme'] 。 '://' 。 $parsed['host'];
        }
        self::$curl_info = array();
        $options = array(
@@ -665,7 +670,7 @@ public static function remote($url, $_count = 0) {
                           if(empty($newurl)) return false;
 
                       if(!strstr($newurl,'http://')){
                               $host   = $uri['scheme']。'://'。$uri['host'];
                               $host   = $parsed['scheme']。'://'。$parsed['host'];
                              $newurl = $host。'/'。$newurl;
                       }
                }      

然而SSRF的常用绕过手法还有302重定向与DNS重新绑定攻击。

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

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 "请求错误:非正常端口,因安全问题只允许抓取80,443端口的链接,如有特殊需求请自行修改程序"。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 "{$url} 请求错误:非正常URL格式,因安全问题只允许抓取 http:// 或 https:// 开头的链接或公有IP地址"。PHP_EOL;
            }
            return false;
        }else{
            return $url;
        }
    }

这里第17行使用了gethostbyname 确定parse_url解析后的host部分,来防护DNS rebinding 攻击。

之前看过一句话:“如果你想搞懂一个漏洞,比较好的方法是:你可以自己先制造出这个漏洞(用代码编写),然后再利用它,最后再修复它”。

代码审计能力和开发能力,将会是未来做安全越来越重要的一项技能。

 

你可能感兴趣的:(php代码审计)