SQL注入之WAF Bypass

针对CTF中SQL注入题里的绕过过滤,Bypass WAF做的一些归纳总结。

前言

之前做SQL注入相关的CTF题,总是有力不从心的感觉,每次都会被过滤搞到自闭,也是因为自己没有好好总结,导致遇到这种过滤题无从下手。。而看别人的wp的话,经常会看到,用/*1*/%0b代替空格,用/*!12345 select 1*/直接进行sql语句注入等等。。每次看到这种,总是云里雾里,网上搜索也不好确定关键词,找不到想要的答案。。

所以今天就来系统总结一波。


起手

select * from user where id = 1 union select 1,2,table_name from information_schema.tables···

上面这条sql语句,是一条正常的SQL注入语句。它的斜体是后台PHP进行的正常查询,斜体后面的语句则是我们的注入语句。不过实践中,肯定会存在WAF,会对我们输入的空格SQL语句关键词做处理。那我今天要研究的就是,如何绕过WAF的检测过滤,重点关注的是绕过WAF对空格的过滤。

既然我们要研究的是绕过空格,那我们把上面的SQL语句按空格的位置来标记分类一下,然后再进行相应研究。

1
select * from user where id=1<位置1>union<位置2>select<位置3>1,2,table_name<位置4>from<位置5>information_schema.tables···

这里先归纳一下,sql语句中空格的代替方法:

1
2
3
4
5
6
/*!50540select user()*/     mysql(独有)内联注释,!后面的数字是版本号,表示当数据库版本>=5.5.40时执行SQL语句
/**/ mysql多行注释
%09,%0a,%0b,%0c,%0d,%20,%a0 一些空白字符
1.1、2.3、1. 浮点数形式
0e1、1e7 科学计数法
+、-、!、@、~、{}、"、'、()、`` 一些特殊字符

上面总结的各种代替空格的方法,只对SQL语句某些位置起局部代替空格作用,并不适用整体SQL语句语法。所以下面就针对上面对sql语句分的位置1-5做具体分析。

在开始测试前,先对本地环境做个记录。

  1. mysql版本:5.5.40
  2. 数据库名:hdu
  3. 表名:user
  4. user表结构
  5. user表内容

位置1——参数跟union之间

先看一下正常查询返回的结果

  1. 使用/**/、和内联注释代替参数跟union中间的空格,关于内联注释里的5位数字在前面以及说过了,这里就不解释了。

  2. 用mysql里的空白字符%09(\t),%0a(\n),%0b,%0c,%0d(\r),%20,%a0代替空格,这里说明一下,因为我这里是在mysql客户端下测试,所以%09、%0a、%0b这种输入肯定是不行的,要转成%0b、%0c这种的url解码结果输入才行。正常SQL注入的话是在网页输入框中输入,浏览器会自动解码,所以填%09,%0a这种形式。
    于是我们把%0b、%0c解码后的字符复制粘贴代替参数和union之间的空格。

  3. 浮点数形式代替空格

  4. 科学计数法代替空格

  5. 特殊符号代替空格,这里测了一下,发现只有()有效,不过挺感觉挺鸡肋的。


位置2——union跟select之间的位置

  1. 注释和mysql内联注释

  2. mysql里的空白字符,同样用%0b,%0c url解码后的字符代替空格进行测试

  3. 浮点数和科学计数法代替空格此位置处不可用

  4. 特殊字符的话,经测试后,也只有()有用。


位置3——select和查询参数之间的位置

  1. 注释和mysql内联注释

  2. mysql里的空白字符,同样用%0b,%0c url解码后的字符代替空格进行测试

  3. 浮点数和科学计数法代替空格此位置处不可用

  4. 特殊字符,经测试~,!,@,+,-,””,’’,{},()可用

    下面是{},()的测试


位置4——查询参数和from之间的空格

  1. 注释和mysql内联注释代替空格可用,此处就不截图了。
  2. mysql里的空白字符,与上面一样,此处不截图
  3. 浮点数和科学计数法代替空格,当与from相邻的查询参数是数字时可用
  4. 特殊字符,这里经测试只有{},(),””,’’,`加字母可用

位置5——from之后的空格

  1. 注释和mysql内联注释代替空格可用,此处就不截图了。
  2. mysql里的空白字符,与上面一样,此处不截图
  3. 浮点数和科学计数法代替空格不可用
  4. 特殊字符,经测试`,{},()可用

实战应用

感觉上面总结的挺垃圾的,想搞个实例再详细写一下,刚好在Jarvis OJ做到一题SQL注入绕过WAF,于是就拿来分析一下。

题目描述:

题目名称:In A Mess
连出题人自己都忘了flag放哪了,只记得好像很混乱的样子。
题目入口:http://web.jarvisoj.com:32780/

题目一进去,查看源码得到提示,访问index.phps文件

访问index.phps文件,得到一份源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php

error_reporting(0);
echo "<!--index.phps-->";

if(!$_GET['id'])
{
header('Location: index.php?id=1');
exit();
}
$id=$_GET['id'];
$a=$_GET['a'];
$b=$_GET['b'];
if(stripos($a,'.'))
{
echo 'Hahahahahaha';
return ;
}
$data = @file_get_contents($a,'r');
if($data=="1112 is a nice lab!" and $id==0 and strlen($b)>5 and eregi("111".substr($b,0,1),"1114") and substr($b,0,1)!=4)
{
require("flag.txt");
}
else
{
print "work harder!harder!harder!";
}


?>

从上面代码的第4行和刚刚index.php页面源码截图来看,很容易推测出,这份代码是index.php的源码。

而这里源码的漏洞和之前Bugku上一题类似,id利用弱类型比较,data利用php://input,b利用ereg的00截断进行绕过如下图,这里就不细讲了,毕竟重头戏是SQL注入。

绕过上面那关之后,可以看到有一个地址^HT2mCpcvOLf,继续访问。

经测试,当将id改为1之外的数的话,会直接显示sql语句

把id改为2-1,会回显hi666,说明id是整数型,这里可注入。

然后尝试进行注入,但是发现当输入注入语句,会回显you bad boy/girl!

结合三种回显,可以梳理一下逻辑。当id存在于服务器数据库时,回显表中的内容,当id不在服务器数据库里时(或者执行出错),回显sql语句,当检测到注入时,回显you bad boy/girl!

说明有waf,存在过滤,经测试,在id后输入空格即会被检测成注入(位置1)。于是可以采用上述的空格绕过姿势进行尝试。
经测试,发现id和union之间的空格可以用/*1*/或者浮点数、科学计数法等绕过,而内联注释,和空注释会被检测。图为用浮点数绕过空格,但是输入了union却发现回显语句没有union,说明union等关键字被替换成空了。

于是输入要用到的sql关键词,看看回显结果,输入id=2.2union;select;and;from;or,回显结果如下图。

可以看到,除了and和or,其余关键词都被替换为空了。这里尝试一下双写、大小写绕过,发现双写和大小写都可以。

解决了id和union直接的空格和关键词过滤后,然后再进一步进行sql注入。

然后发现union和select之间(位置2)要是存在空格的话,空格及后面的内容会被替换为空。

所以这里用到我们在位置2处测试的替换空格方案进行绕过,我测试时用到%0b,/*1*/可以绕过此处空格

然后从上图又发现select和查询参数间空格(位置3)及以后的内容会被替换成功,于是采用我们上面在位置3处分析总结的方法进行尝试,最后发现%0b,%0c可以进行替换,而/*1*/会被检测出注入。

于是用%0b替换select和查询参数间的空格,然后继续注入,逐步增加select后面的数字,发现加到3的时候,页面回显变为了3。。到这里已经看到了成功的希望(2333)

知道3字段位置有回显,然后利用这个字段进行查询。然后再进一步测试,然后发现,只要有空格就会把空格及空格后面的内容置换为空。这里懒得截图了,反正一步步测试下来,发现后面空格都可以用%0b绕过。

这里就跳过后续空格测试验证过程了。
直接爆表,payload:
id=2.2Union/*1*/%0bSelect%0b1,2,group_concat(table_name)%0bFrom%0binformation_schema.tables%0bwhere%0btable_schema=database()

得到表名content。

然后爆列,payload:
id=2.2Union/*1*/%0bSelect%0b1,2,group_concat(column_name)%0bFrom%0binformation_schema.columns%0bwhere%0btable_schema=database()

得到列名id,context,title

然后爆值,payload:
id=2.2Union/*1*/%0bSelect%0b1,2,group_concat(id,context,title)%0bFrom%0bcontent

得到flag:PCTF{Fin4lly_U_got_i7_C0ngRatulation5}


参考

WAF Bypass数据库特性(Mysql探索篇)

ヾノ≧∀≦)o 来呀!快活呀!~
-------- 本文结束 --------