代码审计,顾名思义就是检查源代码中的安全缺陷。通过自动化工具或着人工审查的方式,对程序源代码逐条进行检查和分析,发现源码缺陷引发的安全漏洞,需要提供代码修订措施和修复建议。
以下为php代码审计的一般方法:
1。逐条读代码
2。敏感函数回溯(使用工具审计)
3。定向功能分析
下面进行逐一介绍:
1。 逐条读代码:通过对整个项目的代码进行阅读,从而发现问题,这种方法是最全面的,但也是最麻烦的,最容易出错。
而且大型的程序源码,代码巨大,这种方法会相当耗费时间,所以一般是企业针对自身产品进行审计,不得不说,这种方法很有用,能够了解到整个应用的程序架构及业务逻辑,以此可以挖掘到更多的高质量的漏洞,尤其是容易忽视的逻辑漏洞,对于小型程序源码,也可以使用这种方法进行审计。
2。 敏感函数回溯(使用工具审计):大多数的漏洞主要是php的函数的使用不当造成的,只要找到这些使用不当的函数,就可以快速的发现想要挖掘的漏洞。
这种方法相对比较快速和高效,也可以使用工具进行审计,一般可以用Seay源代码审计系统。原理是利用正则表达式,匹配一些危险的函数、关键函数、敏感关键字,然后得到这些函数,判断代码的上下文,找到参数的源头所在。
3。定向功能分析:有些程序代码量确实不少,通读代码耗时长,效果也不一定好。所以该方法主要是根据程序的业务逻辑和业务功能进行审计的,首先大概浏览网站的页面,比如有上传功能,有浏览功能,可能猜测到这个程序有上传漏洞、XSS漏洞等,可以大概的推测它有哪些漏洞,然后再针对猜测的结果,进行定向分析。 常见的功能漏洞:
程序初始安装漏洞
站点信息泄露
文件上传管理
登录认证、权限管理漏洞
数据库备份漏洞
验证码漏洞等
有以下总结:
首先,不管是什么程序都要把握大局,了解整体的架构。
其次,根据定向功能发对每一项功能进行审计,可以根据网站的架构使用不同的方法进行分析。
最后,可以将敏感函数进行回溯,找到初始定义的函数,发现漏洞的起源地。
举个栗子,拿github上一个cms来说:https://github.com/idreamsoft/iCMS
下载解压缩后,一般来说,大致为以下的目录结构,
admin:后台管理目录
install:网站的安装目录,其中的install.sql为数据库的结构信息
sys:这个目录里面一般存放着配置信息文件和公共函数库,分别为config.php和lib.php
user:这里面记录着用户的一些操作,如用户注册等
index.php:一般为网页的首页文件,也是我们审计的开始
这里入口为http://ip/icms-7.0/admincp.php
账号密码为导进去数据库文件的定义好了数据,密码采用32位MD5加密,若忘记初始密码,则可以重置。
登录进去。
可以在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 "<b>{$url} 请求错误:非正常URL格式,因安全问题只允许抓取 http:// 或 https:// 开头的链接</b>";
echo "<b>{$url} 请求错误:非正常URL格式,因安全问题只允许抓取 http:// 或 https:// 开头的链接或私有IP地址</b>";
}
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 "<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 攻击。
之前看过一句话:“如果你想搞懂一个漏洞,比较好的方法是:你可以自己先制造出这个漏洞(用代码编写),然后再利用它,最后再修复它”。
代码审计能力和开发能力,将会是未来做安全越来越重要的一项技能。
浅谈php代码审计
代码审计,顾名思义就是检查源代码中的安全缺陷。通过自动化工具或着人工审查的方式,对程序源代码逐条进行检查和分析,发现源码缺陷引发的安全漏洞,需要提供代码修订措施和修复建议。
以下为php代码审计的一般方法:
1。逐条读代码
2。敏感函数回溯(使用工具审计)
3。定向功能分析
下面进行逐一介绍:
1。 逐条读代码:通过对整个项目的代码进行阅读,从而发现问题,这种方法是最全面的,但也是最麻烦的,最容易出错。
而且大型的程序源码,代码巨大,这种方法会相当耗费时间,所以一般是企业针对自身产品进行审计,不得不说,这种方法很有用,能够了解到整个应用的程序架构及业务逻辑,以此可以挖掘到更多的高质量的漏洞,尤其是容易忽视的逻辑漏洞,对于小型程序源码,也可以使用这种方法进行审计。
2。 敏感函数回溯(使用工具审计):大多数的漏洞主要是php的函数的使用不当造成的,只要找到这些使用不当的函数,就可以快速的发现想要挖掘的漏洞。
这种方法相对比较快速和高效,也可以使用工具进行审计,一般可以用Seay源代码审计系统。原理是利用正则表达式,匹配一些危险的函数、关键函数、敏感关键字,然后得到这些函数,判断代码的上下文,找到参数的源头所在。
3。定向功能分析:有些程序代码量确实不少,通读代码耗时长,效果也不一定好。所以该方法主要是根据程序的业务逻辑和业务功能进行审计的,首先大概浏览网站的页面,比如有上传功能,有浏览功能,可能猜测到这个程序有上传漏洞、XSS漏洞等,可以大概的推测它有哪些漏洞,然后再针对猜测的结果,进行定向分析。 常见的功能漏洞:
程序初始安装漏洞
站点信息泄露
文件上传管理
登录认证、权限管理漏洞
数据库备份漏洞
验证码漏洞等
有以下总结:
首先,不管是什么程序都要把握大局,了解整体的架构。
其次,根据定向功能发对每一项功能进行审计,可以根据网站的架构使用不同的方法进行分析。
最后,可以将敏感函数进行回溯,找到初始定义的函数,发现漏洞的起源地。
举个栗子,拿github上一个cms来说:https://github.com/idreamsoft/iCMS
下载解压缩后,一般来说,大致为以下的目录结构,
admin:后台管理目录
install:网站的安装目录,其中的install.sql为数据库的结构信息
sys:这个目录里面一般存放着配置信息文件和公共函数库,分别为config.php和lib.php
user:这里面记录着用户的一些操作,如用户注册等
index.php:一般为网页的首页文件,也是我们审计的开始
这里入口为http://ip/icms-7.0/admincp.php
账号密码为导进去数据库文件的定义好了数据,密码采用32位MD5加密,若忘记初始密码,则可以重置
登录进去。
可以在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 "<b>{$url} 请求错误:非正常URL格式,因安全问题只允许抓取 http:// 或 https:// 开头的链接</b>";
echo "<b>{$url} 请求错误:非正常URL格式,因安全问题只允许抓取 http:// 或 https:// 开头的链接或私有IP地址</b>";
}
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 "<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 攻击。
之前看过一句话:“如果你想搞懂一个漏洞,比较好的方法是:你可以自己先制造出这个漏洞(用代码编写),然后再利用它,最后再修复它”。
代码审计能力和开发能力,将会是未来做安全越来越重要的一项技能。