FlawOpen

flawopen.com/Incidents/bricksforge-pro-forms-upload-metadata-october-2026

● Bricksforge WordPress plugin · CVE-2026-85097 · 2026-10-07 · CVSS 9.8 · 严重
安全研究 · FlawOpen

CVE-2026-85097:Bricksforge Pro Forms 上传缺陷

CVE-2026-85097 使未认证请求能够利用 Bricksforge Pro Forms 中已验证的临时文件内容与客户端可控目标元数据之间的不一致。Patchstack 报告了利用尝试;请更新插件并调查暴露的站点。[1], [2], [3], [4], [5]

💡 通俗易懂的原理解析 (ELI5)

想象一个收发室先确认包裹里是无害图片,却允许寄件人在包裹送到某个会把特定标签当程序打开的办公室前更换标签。Bricksforge 检查了临时上传文件,但之后提交表单时仍可提供不同的目标名称。防护办法是让文件身份和存储目标在整个流程中始终由服务器控制。[3], [2]

核心概念与专有名词

临时上传
表单提交完成前暂存的文件。此阶段的检查并不会让客户端之后提交的文件元数据变得可信。[2], [3]
CWE-434
危险类型文件的无限制上传:系统接受了可能被危险解释或执行的文件。[1], [2]
元数据信任边界
应用判断名称、URL、MIME 类型或路径来自可信服务器记录还是不可信客户端的位置。[2], [3]
CVE 评分与利用状态
Wordfence CNA 的 CVE 记录给出 CVSS 3.1 9.8,CISA enrichment 字段显示未记录利用。Patchstack 给出 10.0,并称其遥测在 2026 年 10 月 7 日首次观察到相关活动。这些结论分别归属于各自来源;均不能证明已列入 CISA KEV。[1], [2], [3], [4]
示例代码
下方 PHP 对比仅用于概括验证边界。它不是 Bricksforge 源码,也不是其补丁还原;所引 changelog 未公开这些实现细节。[2], [3], [5]

框架映射

  • 本指南所述弱点的 CWE 参考: CWE-434

报告的版本状态与运维处置

证据来源来源说明防护处置
Wordfence CNA / CVE 记录描述了未认证任意文件上传;记录中的受影响范围截至 3.1.8.9。将其视为 CNA 记录的影响范围,而不是已部署站点安全无虞的证明。
Patchstack(2026 年 10 月 8 日)报告首次于 10 月 7 日观察到利用尝试,并将 3.1.8.10 标为已修复版本。将其视为 Patchstack 报告的修复边界;逐个核实每个站点实际安装的插件版本。
Bricksforge 公开更新日志4.0.1 版列出 Pro Forms 上传安全修复,但未说明 3.1.8.10 这一边界。不要仅凭更新日志推断 4.0.1 是首个修复版本,或某个具体 3.x 版本已经安全。
可从外部访问或疑似受侵的站点来源报告了利用尝试,但没有证明每个被攻击的站点都已失陷。升级到当前受支持的版本;检查上传及 Web 服务器日志,并调查异常文件后再宣布恢复完成。

事件演进时间线

2026-10-07

Wordfence 将其公告的公开发布日期列为 10 月 7 日。Patchstack 称其遥测在当天 21:47 UTC 首次观察到攻击尝试;公告发布与遥测观察是不同事件。[2], [3]

2026-10-08

CVE 记录将 datePublished 列为 10 月 8 日,比 Wordfence 标注的 10 月 7 日公开发布日期晚一天。Patchstack 的活动报告也注明于 10 月 8 日发布;这些是不同记录和报告的日期。[1], [2], [3]

2026-10-08

Patchstack 在 10 月 8 日的分析中描述了验证不一致和观察到的尝试,并将 3.1.8.10 列为已修复版本。Bricksforge 变更日志另列出 4.0.1 中 Pro Forms 上传安全修复,但未确定 3.x 系列的确切修复版本。[3], [4], [5]

攻击执行流程分解

步骤 1

临时文件通过初始检查

CNA 描述了先前的临时上传通过 MIME 验证的情况。通过该检查并不意味着之后可以任意更改文件名或目标位置。[2], [1]

步骤 2

表单提交提供另一组元数据

Patchstack 称后续表单处理信任客户端提交的 temporaryFileUploads 元数据,其中 URL 可能与已经验证的文件不一致。问题在于已检查的内容与未检查的目标元数据采用了不同信任级别。[2], [3]

步骤 3

文件被放到危险名称下

如果服务器使用客户端控制的目标来完成上传,且目标允许执行,危险扩展名可能导致服务器解释文件。本说明只概述机制;理解问题无需请求格式或攻击载荷。[2], [3]

步骤 4

暴露可能导致服务器受控

CNA 表示该问题可能允许任意 PHP 执行。Patchstack 报告了利用尝试并建议更新。其观察不能证明每个目标站点都已被入侵,也无法确定攻击者身份。[2], [3]

源代码对比:漏洞与安全实现

此通用示例展示由服务器管理的上传身份、按用户查询、重新检查内容类型、生成文件名和私有存储。它不是 Bricksforge 源码或其实际补丁。生产代码还必须实施大小限制、安全权限、原子状态转换和符合上下文的文件提供方式;受影响站点应更新到供应商支持的已修复版本。[2], [3]

存在漏洞的实现
function finalize_upload( array $item, string $stagedPath, string $uploadDir ): string {
    // 客户端元数据会在此前的 MIME 检查之后决定最终文件名。
    $name = basename( (string) parse_url( $item['file']['url'], PHP_URL_PATH ) );
    $destination = $uploadDir . DIRECTORY_SEPARATOR . $name;
    if ( ! rename( $stagedPath, $destination ) ) {
        throw new RuntimeException( 'Could not finalize upload' );
    }
    return $destination;
}
加固后的安全修复
function finalize_upload( string $uploadId, UploadRegistry $registry, string $privateDir ): string {
    // 从服务器端记录解析所有者和路径,绝不依赖客户端 URL。
    $upload = $registry->findPendingForCurrentUser( $uploadId );
    if ( $upload === null || ! is_file( $upload->path ) ) {
        throw new RuntimeException( 'Unknown pending upload' );
    }
    $types = [ 'image/jpeg' => 'jpg', 'image/png' => 'png' ];
    $mime = ( new finfo( FILEINFO_MIME_TYPE ) )->file( $upload->path );
    if ( ! isset( $types[ $mime ] ) ) {
        throw new RuntimeException( 'Unsupported file type' );
    }
    $destination = $privateDir . DIRECTORY_SEPARATOR . bin2hex( random_bytes( 16 ) ) . '.' . $types[ $mime ];
    if ( ! rename( $upload->path, $destination ) ) {
        throw new RuntimeException( 'Could not finalize upload' );
    }
    $registry->markConsumed( $uploadId );
    return $destination;
}

工程与系统安全加固清单

参考来源