
Laboratório Docker A/B + PoC para CVE-2026-32475 (upload arbitrário de arquivos não autenticado no Elementor Pro Forms -> RCE via dessincronização do loop de validação/movimentação)
Laboratório A/B em Docker que reproduz a CVE-2026-32475 (CVSS 9.x, Patchstack; campo File Upload do Elementor Pro Forms, afetado ≤ 4.2.1, corrigido na 4.2.2 em 2026-08-19; relatada por Tin Pham / TF1T). Não autenticado, sem interação do usuário.
Somente para testes autorizados / fins educacionais. Este laboratório roda inteiramente em
127.0.0.1em contêineres Docker descartáveis. Não aponte o PoC para sistemas nos quais você não tenha autorização explícita para testar. Obuild.shbusca o código-fonte do Elementor Pro em um espelho GPL público; nenhum código premium é redistribuído neste repositório.
modules/forms/fields/upload.php. Dois loops iteram sobre o mesmo $_FILES['form_fields'][$id] remodelado, mas tratam uma parte vazia (UPLOAD_ERR_NO_FILE) de forma diferente:
// validation() — linha ~269
foreach ( $_FILES['form_fields'][ $id ] as $index => $file ) {
if ( ! $field['required'] && UPLOAD_ERR_NO_FILE === $file['error'] ) {
return; // <-- abandona TODA a validação restante deste campo
}
...
if ( ! $this->is_file_type_valid( $field, $file ) ) { // lista de permissão/negação de extensão
$ajax_handler->add_error( $id, 'This file type is not allowed.' );
}
}
// process_field() — linha ~418
foreach ( $_FILES['form_fields'][ $id ] as $index => $file ) {
if ( UPLOAD_ERR_NO_FILE === $file['error'] ) {
continue; // <-- pula SOMENTE a parte vazia, continua o loop
}
$filename = uniqid() . '.' . pathinfo( $file['name'], PATHINFO_EXTENSION ); // extensão do atacante, sem nova verificação
move_uploaded_file( $file['tmp_name'], trailingslashit( $uploads_dir ) . $filename );
}
Envie um campo de upload opcional com duas partes — uma vazia primeiro, depois o payload PHP. validation() encontra a parte vazia e executa return antes de verificar o .php; process_field() executa continue além da parte vazia e move o .php para wp-content/uploads/elementor/forms/<uniqid()>.php. A lista de bloqueio get_blacklist_file_ext() (php,phtml,pht,shtml,…) só é executada dentro de validation(), portanto é completamente contornada.
Totalmente não autenticado: wp_ajax_nopriv_elementor_pro_forms_send_form (ajax-handler.php:295).
get_ensure_upload_dir() cria um .htaccess no diretório de forms contendo apenas Options -Indexes + Header set Content-Disposition attachment. Isso é uma dica de download no navegador — no Apache + mod_php o .php ainda executa no lado do servidor; o atacante (curl) simplesmente recebe a saída do comando com um cabeçalho de download. O módulo de forms não contém limpeza com unlink, portanto o shell persiste.
Uma página publicada com um widget Form do Elementor Pro que tenha um campo File Upload cuja opção Obrigatório = Não (o ramo ! $field['required']). A configuração do campo é lida no lado do servidor a partir de _elementor_data, então o atacante não pode alterná-la — mas campos de "anexo opcional" são comuns.
Nome armazenado = uniqid() = %08x%05x = (segundo unix)(microssegundo). O segundo é vazado exatamente pelo cabeçalho de resposta HTTP Date; apenas o microssegundo (0–999999) é desconhecido → uma força bruta online limitada de ≤10⁶ GETs. poc.py --recover ancora o microssegundo no tempo de chegada da resposta (co-localizado / sincronizado com NTP ⇒ segundos–minutos; aqui: ~26 mil requisições) e transforma a escrita em execução remota de código.
| Serviço | Porta | Elementor Pro |
|---|---|---|
wp-vuln | http://127.0.0.1:8975 | 3.6.4 — vulnerável (árvore autêntica; dessincronização do validation() idêntica à ≤4.2.1) |
wp-patched | http://127.0.0.1:8976 | 3.6.4 com a correção equivalente à 4.2.2 (return → continue, alinhando os loops) |
Stack: wordpress:php7.4-apache (mod_php) + MariaDB 10.6 + Elementor (gratuito) 3.6.8 + Elementor Pro 3.6.4.
Cada variante tem um admin (admin/labpass) e uma página publicada "CVE-2026-32475 Lab" (post_id=5, form_id=frm00001, campo de upload field_cv, Obrigatório=Não).
Nota sobre a versão. Nenhum código-fonte limpo 4.2.1/4.2.2 é publicamente redistribuível, então o laboratório executa a árvore autêntica 3.6.4, cuja dessincronização
validation()/process_field()é byte-idêntica ao código descrito pela CVE-2026-32475 (a falha é latente há muito tempo; a 4.2.2 alinhou os dois loops). A compilação A/B "corrigida" aplica exatamente esse alinhamento.
Requer Docker + Docker Compose e rede de saída (para baixar imagens, o Elementor gratuito e o espelho do código-fonte do Elementor Pro). O build.sh clona automaticamente a árvore autêntica do Elementor Pro.
git clone https://github.com/dinosn/cve-2026-32475-elementor-pro-lab
cd cve-2026-32475-elementor-pro-lab
bash build.sh # clona o plugin + compose up + instala WP/Elementor + cria a página do form (ambas as variantes)
bash verify.sh # prova A/B: a vulnerável grava+executa um shell; a corrigida rejeita. Limpa tudo.
# RCE não autenticado totalmente remoto (extrai automaticamente post_id/form_id/field_id da página):
python3 poc.py -t http://127.0.0.1:8975 --page-id 5 --recover -c "id; uname -a"
# apenas comprove o contorno da lista de bloqueio (deixe o shell para inspeção):
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 autêntico (vulnerável); gerado pelo build.sh, ignorado pelo gitelementor-pro-patched/ — mesma árvore, validation() return→continue; gerado pelo patch_pro.sh, ignorado pelo gitevidence/verify_transcript.txt — uma execução capturada do verify.sh com sucessoinstall_wp.sh — instalação via wp-cli do WP + Elementor gratuito + ativa o Pro + cria a página do form + corrige permissões de uploadssetup_page.php — constrói a página do form do Elementor com um campo de upload opcionalpatch_pro.sh — deriva a árvore corrigida + imprime o diff de uma linhapoc.py — upload dessincronizado não autenticado + recuperação do nome de arquivo uniqid() + RCEverify.sh — prova A/B com um oráculo de sistema de arquivos independente (prova a execução, não apenas a escrita)build.sh — compilação únicacd /root/cve-2026-32475-lab && docker compose down -v