# CVE-2026-32475 的 A/B Docker 实验室 + PoC(Elementor Pro Forms 未认证任意文件上传 -> 通过验证/移动循环失步实现 RCE)
一个A/B Docker实验环境,用于复现CVE-2026-32475(CVSS 9.x,Patchstack;Elementor Pro 表单文件上传字段,受影响版本 ≤ 4.2.1,2026-08-19在4.2.2中修复;由 Tin Pham / TF1T报告)。未认证,无需用户交互。
仅限授权测试/教育用途。 本实验环境完全运行在
127.0.0.1上的一次性 Docker容器中。请勿将PoC指向未经明确授权测试的系统。build.sh从公共GPL镜像获取Elementor Pro源码;本仓库不重新分发任何付费代码。
modules/forms/fields/upload.php。两个循环遍历相同的重塑后
$_FILES['form_fields'][$id],但对空部分(UPLOAD_ERR_NO_FILE)的处理方式不同:
// validation() — 约第269行
foreach ( $_FILES['form_fields'][ $id ] as $index => $file ) {
if ( ! $field['required'] && UPLOAD_ERR_NO_FILE === $file['error'] ) {
return; // <-- 放弃该字段的所有剩余验证
}
...
if ( ! $this->is_file_type_valid( $field, $file ) ) { // 扩展名允许/拒绝列表
$ajax_handler->add_error( $id, 'This file type is not allowed.' );
}
}
// process_field() — 约第418行
foreach ( $_FILES['form_fields'][ $id ] as $index => $file ) {
if ( UPLOAD_ERR_NO_FILE === $file['error'] ) {
continue; // <-- 仅跳过空部分,继续处理
}
$filename = uniqid() . '.' . pathinfo( $file['name'], PATHINFO_EXTENSION ); // 攻击者扩展名,无重新检查
move_uploaded_file( $file['tmp_name'], trailingslashit( $uploads_dir ) . $filename );
}
发送一个可选上传字段,包含两个部分 — 先是一个空部分,然后是PHP
载荷。validation()遇到空部分并在检查.php之前return;
process_field()``continue跳过空部分并将.php移动到
wp-content/uploads/elementor/forms/<uniqid()>.php。get_blacklist_file_ext()黑名单
(php,phtml,pht,shtml,…)仅在validation()内运行,因此被完全绕过。
完全未认证:wp_ajax_nopriv_elementor_pro_forms_send_form(ajax-handler.php:295)。
get_ensure_upload_dir()在表单目录中放置一个.htaccess,仅包含
Options -Indexes + Header set Content-Disposition attachment。这是一个浏览器下载
提示 — 在Apache + mod_php上,.php仍然在服务器端执行;攻击者(curl)只需
接收带有下载头的命令输出。表单模块没有unlink清理,因此shell持久存在。
一个已发布的页面,包含Elementor Pro 表单小部件,其文件上传字段的
必填 = 否(! $field['required']分支)。字段配置从
_elementor_data在服务器端读取,因此攻击者无法切换 — 但"可选附件"字段很常见。
存储名称 = uniqid() = %08x%05x = (Unix秒)(微秒)。秒通过HTTP Date响应头精确
泄露;只有微秒(0–999999)未知 → 有界的≤10⁶在线GET暴力破解。poc.py --recover将微秒锚定在
响应到达时间上(同地/NTP同步 ⇒ 秒–分钟;此处:约26k请求),并将写入
转化为远程代码执行。
| 服务 | 端口 | Elementor Pro |
|---|---|---|
wp-vuln | http://127.0.0.1:8975 | 3.6.4 — 易受攻击(真实代码树;validation()不同步与≤4.2.1相同) |
wp-patched | http://127.0.0.1:8976 | 3.6.4,带有4.2.2等效修复(return → continue,对齐循环) |
技术栈:wordpress:php7.4-apache(mod_php)+ MariaDB 10.6 + Elementor(免费版)3.6.8 + Elementor Pro 3.6.4。
每个变体都有一个管理员(admin/labpass)和一个已发布的页面**"CVE-2026-32475 Lab"**
(post_id=5,form_id=frm00001,上传字段field_cv,必填=否)。
版本说明。 没有干净的4.2.1/4.2.2源码可公开重新分发,因此实验环境运行真实的 3.6.4代码树,其
validation()/process_field()不同步与CVE-2026-32475描述的 代码逐字节相同(该漏洞长期潜伏;4.2.2对齐了两个循环)。A/B "已修补"构建应用了完全相同的对齐。
需要Docker + Docker Compose以及出站网络(用于拉取镜像、Elementor免费版和
Elementor Pro源码镜像)。build.sh自动克隆真实的Elementor Pro代码树。
git clone https://github.com/dinosn/cve-2026-32475-elementor-pro-lab
cd cve-2026-32475-elementor-pro-lab
bash build.sh # 克隆插件 + compose up + 安装WP/Elementor + 创建表单页面(两个变体)
bash verify.sh # A/B证明:易受攻击版本写入+执行shell;已修补版本拒绝。自动清理。
# 完全远程未认证RCE(自动从页面提取post_id/form_id/field_id):
python3 poc.py -t http://127.0.0.1:8975 --page-id 5 --recover -c "id; uname -a"
# 仅证明黑名单绕过(保留shell以供检查):
python3 poc.py -t http://127.0.0.1:8975 --post-id 5 --form-id frm00001 --field-id field_cv
docker-compose.yml — 2×(WordPress + MariaDB),易受攻击:8975 / 已修补:8976elementor-pro-vuln/ — 真实Elementor Pro 3.6.4(易受攻击);由build.sh生成,git忽略elementor-pro-patched/ — 相同代码树,validation() return→continue;由patch_pro.sh生成,git忽略evidence/verify_transcript.txt — 捕获的通过verify.sh运行记录install_wp.sh — wp-cli安装WP + Elementor免费版 + 激活Pro + 创建表单页面 + 修复上传权限setup_page.php — 构建带有可选上传字段的Elementor表单页面patch_pro.sh — 派生已修补代码树 + 打印单行差异poc.py — 未认证不同步上传 + uniqid()文件名恢复 + RCEverify.sh — 使用独立文件系统预言机的A/B证明(证明执行,而不仅仅是写入)build.sh — 一键构建cd /root/cve-2026-32475-lab && docker compose down -v