Challenge
The page collects nine
KEY{<32-hex>}values and renders a flag image.页面收集九个
KEY{<32-hex>}值,并渲染一张 flag 图片。
http://webhacking.kr:10022/页面标题是 Get 4XX for flag,表单字段为 key1 到 key9,每个字段的客户端 pattern 是 KEY\{([0-9]|[a-f]){32}\},页面引用 /img/flag.png。9 个 key 全部正确时表单返回 Your score : 9,并打印 FLAG is flag{<value>};提交顺序不影响计分(正序与逆序都是 9 分)。
Solution
key 由 HTTP 4xx 状态码发放:任何让 Apache 返回 4xx 的请求都会在响应体里给出该状态码对应的 key,格式是 4XX Error!<br> your key : <key-value>。同一状态码的 key 固定不变,所以凑齐 9 个不同的 4xx 状态码就能得到全部 9 个 key。
实测的状态码、触发请求与对应 key:
400:发送包含 9 KBX-Big请求头的请求,得到KEY{10fac9b9f4112a1d9a650fec275bf164}。403:访问禁止列目录的/img/,得到KEY{cd79de80772c1873bcf63e41e3379c6f}。404:访问不存在的路径,得到KEY{23c0b3a9ccc44b72f17a99eadb351d2f}。405:发送TRACE /,得到KEY{e5602408f2037c05bbbb0995fec6bc58}。408:发送请求行和Host后保持连接,不补完请求头,得到KEY{e44fa3e1865a3839cbc0b658f1ae08cf}。412:请求静态文件/img/flag.png并设置If-Match: "nope",得到KEY{cd7609461c0dbe41a9137056fa4085e2}。414:发送约 30 KB 的超长请求路径,得到KEY{d1617527ac2143863bc347c6123ed921}。416:设置Range: bytes=999999999-,得到KEY{622eced9aa42670c10ee74d29e58e5eb}。417:发送POST /并设置Expect: foo,得到KEY{ed2dd6cb38fe6a4a10e46d22d20047e6}。
Apache 负责生成这些 4xx 响应。405 需要使用不被允许的 method。412 依赖带 ETag 的静态文件;对 / 设置 If-Match 返回 200。417 要求 Expect 值不是 100-continue,后者会返回 100 Continue 并继续正常请求。408 需要原始 socket 在发送部分请求头后保持连接。Apache 2.4.29 对超长 header 返回 400,不产生 431。
收集脚本(原始 socket,因为 408 需要在发完部分请求头后等待):
#!/usr/bin/env python3import reimport socket
HOST, PORT = "webhacking.kr", 10022H = b"Host: webhacking.kr:10022\r\n"
REQUESTS = { 400: b"GET / HTTP/1.1\r\n" + H + b"X-Big: " + b"A" * 9000 + b"\r\n\r\n", 403: b"GET /img/ HTTP/1.1\r\n" + H + b"\r\n", 404: b"GET /definitely-not-here HTTP/1.1\r\n" + H + b"\r\n", 405: b"TRACE / HTTP/1.1\r\n" + H + b"\r\n", 408: b"GET / HTTP/1.1\r\n" + H, # 不补完请求头 412: b"GET /img/flag.png HTTP/1.1\r\n" + H + b'If-Match: "nope"\r\n\r\n', 414: b"GET /" + b"a" * 30000 + b" HTTP/1.1\r\n" + H + b"\r\n", 416: b"GET / HTTP/1.1\r\n" + H + b"Range: bytes=999999999-\r\n\r\n", 417: b"POST / HTTP/1.1\r\n" + H + b"Content-Length: 5\r\nExpect: foo\r\n\r\n",}
23 collapsed lines
def fetch(status: int, raw: bytes, wait: float = 4.0) -> str: sock = socket.create_connection((HOST, PORT), timeout=wait + 8) sock.sendall(raw) sock.settimeout(wait) data = b"" try: while len(data) < 8000: chunk = sock.recv(4096) if not chunk: break data += chunk except socket.timeout: pass sock.close() body = data.decode("utf-8", "replace") match = re.search(r"KEY\{[0-9a-f]{32}\}", body) if not match: raise RuntimeError(f"{status}: no key in response") return match.group(0)
def main() -> None: keys = {status: fetch(status, raw) for status, raw in REQUESTS.items()} for status, key in sorted(keys.items()): print(status, key) query = "&".join(f"key{i}={key}" for i, key in enumerate(keys.values(), 1)) print(f"http://webhacking.kr:10022/?{query}")
if __name__ == "__main__": main()复现边界:入口、9 个状态码、9 个 key 值、Your score : 9 与 flag 回执均为现网实测;408 依赖服务端超时、412 依赖静态文件的 ETag,若实例的 Apache 配置变化需重新确认触发方式。
flag{iViZGYM7K5I}