从一个Laravel SQL注入漏洞开始的Bug Bounty之旅

本文讲述了作者在审计Cachet(基于Laravel框架的状态页面系统)时发现的SQL注入漏洞和利用流程,包括代码审计、漏洞利用和Bug Bounty实践。通过对Cachet的代码审查,作者揭示了Laravel中潜在的安全风险,并分享了如何利用这些漏洞在某些情况下执行任意代码。最后,文章提及了漏洞报告的时间线和受影响的厂商。
摘要由CSDN通过智能技术生成

事先声明:本次测试过程完全处于本地或授权环境,仅供学习与参考,不存在未授权测试过程。本文提到的漏洞《Cachet SQL注入漏洞(CVE-2021-39165)》已经修复,也请读者勿使用该漏洞进行未授权测试,否则作者不承担任何责任

0x01 故事的起源

一个百无聊赖的周日晚上,我在知识星球闲逛,发现有一个匿名用户一连向我提出了两个问题:

本来不是很想回答这两个问题,一是感觉比较基础,二是现在大部分人都卷Java去了,关注PHP的其实不多。不过我搜索了一下自己的星球,发现我的确没有讲过如何调试PHP代码,那么回答一下这个问题也未尝不可。

既然如此,我就打开自己常用的PHP IDE之一PHPStorm(另一款是VSCode),看了看硬盘里落满灰尘的PHP代码,要不就是几年前的版本要不就是没法做演示的非开源项目。如果要新写一篇教程,最好还是上网上找个新的CMS做演示。

于是我打开了Github,搜索“PHP”关键字,点进了PHP这个话题。PHP话题下有几类开源项目,一是一些PHP框架和库,排在前面的主要是Laravel、symfony、Yii、guzzle、PHPMailer、composer等;二是CMS和网站应用,排在前面的有matomo、nextcloud、monica、Cachet等;三是一些README和教学项目,比如awesome-php、DesignPatternsPHP等。

做演示自然选择开箱即用的第二类,于是我挑了一个功能常见且简单的Cachet。

当天晚上我自己搭建、调试、运行起了Cachet这个CMS,并写了一篇简单的教程发在星球里:

本来这个故事到此就结束了,但是不安分的我当时就在想,既然搭都搭起来了,那不如就对其做一遍审计吧。

0x02 Cachet代码审计

Cachet是一款基于Laravel框架开发的状态页面(Statuspage)系统。Statuspage是云平台流行后慢慢兴起的一类系统,作用是向外界展示当前自己各个服务是否在正常运行。国外很多大型互联网平台都有Statuspage,最著名的有 Github、Twitter、Facebook、Amazon AWS等。

Statuspage中占据领导地位的是Statuspage.io,隶属于Atlassian。但毕竟这是一个付费的系统,Cachet得益于自己开源的优势,也有不少拥趸,在Github上有12k多关注。

Cachet最新的稳定版本是2.3.18,基于Laravel 5.2开发,我将其拉下来安装好后开始审计。

经过验证,dev版本的代码可能有所差异(主要是后台getshell部分的POC利用链不一样),本文仅基于稳定版做审计。

Laravel框架的CMS审计,我主要关注下面几个点:

  • 网站路由

  • 控制器(app/Http/Controllers)

  • 中间件(app/Http/Middleware)

  • Model(app/Models)

  • 网站配置(config)

  • 第三方扩展(composer.json)

先从路由开始看起,以app/Http/Routes/StatusPageRoutes.php为例:

$router->group(['middleware' => ['web', 'ready', 'localize']], function (Registrar $router) {
    $router->get('/', [
        'as'   => 'status-page',
        'uses' => 'StatusPageController@showIndex',
    ]);

    $router->get('incident/{incident}', [
        'as'   => 'incident',
        'uses' => 'StatusPageController@showIncident',
    ]);

    $router->get('metrics/{metric}', [
        'as'   => 'metrics',
        'uses' => 'StatusPageController@getMetrics',
    ]);

    $router->get('component/{component}/shield', 'StatusPageController@showComponentBadge');
});

其中可以看出的信息是:

  • 某个path所对应的Controller和方法

  • 整个模块使用的中间件

前者比较好理解,中间件的作用通常是做权限的校验、全局信息的提取等。这个route组合用了三个中间件web、ready和localize。我们可以在app/Http/Kernel.php找到这三个名字对应的中间件类,他们的作用是:

  • web是多个中间件的组合,作用主要是设置Cookie和session、校验csrf token等

  • ready用于检查当前CMS是否有初始化,如果没有,则跳到初始化的页面

  • localize主要用于根据请求中的Accept-Language来展示不同语言的页面

接着我会主要关注那些不校验权限的Controller(就是没有admin和auth中间件的Controller)。我关注到了app/Http/Controllers/Api/ComponentController.php的getComponents方法:

/**
  * Get all components.
  *
  * @return \Illuminate\Http\JsonResponse
  */
public function getComponents()
{
    if (app(Guard::class)->check()) {
        $components = Component::query();
    } else {
        $components = Component::enabled();
    }

    $components->search(Binput::except(['sort', 'order', 'per_page']));

    if ($sortBy = Binput::get('sort')) {
        $direction = Binput::has('order') && Binput::get('order') == 'desc';

        $components->sort($sortBy, $direction);
    }

    $components = $components->paginate(Binput::get('per_page', 20));

    return $this->paginator($components, Request::instance());
}

其中有两个关键点:

  • $components->search(Binput::except(['sort', 'order', 'per_page']));

  • $components->sort($sortBy, $direction);

sort和search方法都不是Laravel自带的Model方法,这种情况一般是自定义的scope。scope是定义在Model中可以被重用的方法,他们都以scope开头。我们可以在app/Models/Traits/SortableTrait.php中找到scopeSort方法:

trait SortableTrait
{
    /**
     * Adds a sort scope.
     *
     * @param \Illuminate\Database\Eloquent\Builder $query
     * @param string                                $column
     * @param string                                $direction
     *
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeSort(Builder $query, $column, $direction)
    {
        if (!in_array($column, $this->sortable)) {
            return $query;
        }

        return $query->orderBy($column, $direction);
    }
}

$column经过了in_array的校验,$direction传入的是bool类型&#

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值