首页 网络安全 正文

Statamic CMS敏感信息泄露漏洞CVE-2026-71293

摘要

栋科技漏洞库关注到Statamic CMS通过 Antlers current_user 变量不受保护地暴露2FA,追踪为CVE-2026-71293,CVSS 3.X评分6.2。

Statamic 诞生于 2012 年,基于Laravel开发的现代化混合架构 CMS,主打文件存储优先、可选数据库、兼顾开发者自由与编辑易用性。

一、基本情况

Core内核永久免费,开放全部核心CMS功能,个人、商用均免费使用,前端完全自主可控,没有预设主题捆绑,适合定制化品牌官网。

NGINX regex map 内存漏洞CVE-2026-42533

Statamic是海外主流扁平化(Flat File)内容管理系统,分为免费开源内核版、商用 Pro 授权版,后台UI现代简洁,支持内容实时预览。

栋科技漏洞库关注到Statamic CMS通过 Antlers current_user 变量不受保护地暴露2FA,追踪为CVE-2026-71293,CVSS 3.X评分6.2。

二、漏洞分析

CVE-2026-71293是Statamic CMS 信息泄露漏洞,由于模板变量 current_user 缺少防护,双因子认证(2FA)应急恢复码被直接读取。

密码这类敏感字段原本做了隐藏防护,唯独恢复码漏掉了保护规则,攻击者拿到恢复码即可绕过二次验证登录账号,盗取管理员权限。

Statamic CMS的用户增强解析器AugmentedUser::get()在src/Auth/AugmentedUser.php中,

包含一个显式的“two_factor_recovery_codes”句柄案例,该句柄返回用户的原始双因素恢复代码,

没有访问限制:

“if($handle==='two_factor-recovery_codes'){返回新值($this->data->get('two_factory_recovery_codes'),…);}`。

与完全从AugmentedUser中排除的敏感字段(如password/password_hash)不同,

two_factor_recovery_codes既没有从Augmented User中排除,

也没有出现在Statamic的Antlers变量保护列表中:

src/Providers/ViewServiceProvider.php中的guardVariablePatterns/guardContentVariablePatterns和运行时GlobalRuntimeState保护路径

默认情况下只有guard config.app.key。

在为给定字段启用原始/动态模板渲染的任何Antlers模板字段上(管理员/开发人员配置的蓝图级字段选项),

诸如“{current_user.two_factor_recovery_codes}{value}|{/current_user.tw_factor_reivery_codes}”之类的模板,

将查看用户自己的2FA恢复代码直接渲染到HTML响应中,

允许可以查看或捕获该响应的攻击者(例如通过共享/可观察页面,或特制的链接导致受害者渲染)获取代码并绕过2FA。

触发条件:网站开启了动态模板渲染功能,登录任意账号后,写入一段简易模板代码就能读出本人的 2FA 救援码。

因此利用该漏洞需要在目标用户数据流经的字段上启用动态Antlers渲染,这是一种蓝图配置权限,而不是标准的内容编辑权限。

src/Auth/AugmentedUser.php
<?php

namespace Statamic\Auth;

use Statamic\Data\AbstractAugmented;
use Statamic\Facades\Role;
use Statamic\Facades\User;
use Statamic\Facades\UserGroup;
use Statamic\Fields\Value;
use Statamic\Support\Str;

class AugmentedUser extends AbstractAugmented
{
    private $cachedKeys;

    public function keys()
    {
        if ($this->cachedKeys) {
            return $this->cachedKeys;
        }

        return $this->cachedKeys = $this->data->data()->keys()
            ->merge(collect($this->data->supplements() ?? [])->keys())
            ->merge($this->commonKeys())
            ->merge($this->roleHandles())
            ->merge($this->groupHandles())
            ->merge($this->blueprintFields()->keys())
            ->unique()->sort()->values()->all();
    }

    private function commonKeys()
    {
        return [
            'id',
            'name',
            'title',
            'email',
            'initials',
            'edit_url',
            'is_user',
            'last_login',
            'avatar',
            'api_url',
            'preferred_locale',
        ];
    }

    public function get($handle): Value
    {
        if ($handle === 'is_user') {
            return new Value(true, 'is_user', null, $this->data);
        }

        if ($handle === 'is_super') {
            return new Value($this->data->isSuper(), 'is_super', null, $this->data);
        }

        if ($handle === 'two_factor_recovery_codes') {
            return new Value($this->data->get('two_factor_recovery_codes'), 'two_factor_recovery_codes', null, $this->data);
        }

        if (Str::startsWith($handle, 'is_')) {
            return new Value(in_array(Str::after($handle, 'is_'), $this->roles()), $handle, null, $this->data);
        }

        if (Str::startsWith($handle, 'in_')) {
            return new Value(in_array(Str::after($handle, 'in_'), $this->groups()), $handle, null, $this->data);
        }

        return parent::get($handle);
    }

    protected function roles()
    {
        return $this->data->roles()->map->id()->values()->all();
    }

    protected function groups()
    {
        return $this->data->groups()->map->id()->values()->all();
    }

    protected function roleHandles()
    {
        return Role::all()->map(function ($role) {
            return 'is_'.$role->handle();
        })->values()->all();
    }

    protected function groupHandles()
    {
        return UserGroup::all()->map(function ($group) {
            return 'in_'.$group->handle();
        })->values()->all();
    }

    protected function initials()
    {
        if (! $this->data->hasQueriedColumn('name')) {
            $user = User::query()
                ->where('id', $this->data->id())
                ->get(['name'])
                ->first();

            $this->data->set('name', $user->get('name'));
        }

        return $this->data->initials();
    }

    protected function avatar()
    {
        return $this->data->hasAvatarField() ? $this->data->avatarFieldValue() : $this->data->gravatarUrl();
    }

    protected function preferredLocale()
    {
        return $this->data->preferredLocale();
    }

    public function notifications()
    {
        return $this->data->get('notifications');
    }
}

三、影响范围

未知

四、修复建议

未知

五、参考链接

管理员已设置登录后刷新可查看



扫描二维码,在手机上阅读
评论
更换验证码
友情链接