
A/B Docker 랩 + CVE-2026-32475용 PoC (Elementor Pro Forms 인증되지 않은 임의 파일 업로드 -> 검증/이동 루프 불일치를 통한 RCE)
CVE-2026-32475(CVSS 9.x, Patchstack, Elementor Pro Forms File Upload 필드, 영향 ≤ 4.2.1, 2026-08-19에 4.2.2에서 수정, Tin Pham / TF1T 보고)을 재현하는 A/B Docker 랩입니다. 인증되지 않았으며 사용자 상호작용이 필요 없습니다.
승인된 테스트/교육 전용입니다. 이 랩은 폐기 가능한 Docker 컨테이너에서 전적으로
127.0.0.1에서 실행됩니다. 명시적으로 테스트 승인을 받지 않은 시스템에 PoC를 사용하지 마십시오.build.sh는 공개 GPL 미러에서 Elementor Pro 소스를 가져옵니다. 이 저장소에는 프리미엄 코드가 재배포되지 않습니다.
modules/forms/fields/upload.php. 두 루프가 동일한 재구성된 $_FILES['form_fields'][$id]를 반복하지만 빈 부분(UPLOAD_ERR_NO_FILE)을 다르게 처리합니다:
// validation() — line ~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() — line ~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()는 forms 디렉토리에 Options -Indexes + Header set Content-Disposition attachment만 포함된 .htaccess를 생성합니다. 이는 브라우저 다운로드 힌트입니다 — Apache + mod_php에서 .php는 여전히 서버 측에서 실행됩니다. 공격자(curl)는 다운로드 헤더와 함께 명령 출력을 수신할 뿐입니다. forms 모듈에는 unlink 정리 코드가 없으므로 셸이 지속됩니다.
Required = No(! $field['required'] 분기)인 File Upload 필드가 있는 Elementor Pro Form 위젯이 포함된 게시된 페이지. 필드 구성은 _elementor_data에서 서버 측으로 읽히므로 공격자가 이를 전환할 수 없지만 — "선택적 첨부 파일" 필드는 일반적입니다.
저장된 이름 = uniqid() = %08x%05x = (유닉스 초)(마이크로초). 초는 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 | 4.2.2와 동등한 수정이 적용된 3.6.4 (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, Required=No)이 있습니다.
버전 참고. 깨끗한 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 증명: 취약 버전은 셸을 쓰고 실행, 패치 버전은 거부. 정리 수행.
# 완전 원격 인증되지 않은 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-ignoredelementor-pro-patched/ — 동일 트리, validation() return→continue; patch_pro.sh로 생성, git-ignoredevidence/verify_transcript.txt — 캡처된 통과 verify.sh 실행 기록install_wp.sh — wp-cli로 WP + Elementor 무료 설치 + Pro 활성화 + 폼 페이지 생성 + 업로드 권한 수정setup_page.php — 선택적 업로드 필드가 있는 Elementor 폼 페이지 구축patch_pro.sh — 패치된 트리 생성 + 한 줄 diff 출력poc.py — 인증되지 않은 불일치 업로드 + uniqid() 파일 이름 복구 + RCEverify.sh — 독립적인 파일시스템 오라클을 사용한 A/B 증명 (쓰기만이 아닌 실행 증명)build.sh — 원샷 빌드cd /root/cve-2026-32475-lab && docker compose down -v