WordPress Core <= 7.1.1 비인증 LFI에서 RCE로 - 검증 랩, PoC, nuclei 템플릿 (GHSA-7hp8-65ch-5whp)
격리된 루프백 전용 Docker 랩에서 CVE-2026-87902 (GHSA-7hp8-65ch-5whp)를 재현하고 검증하기 위한 보안 연구 자료: WordPress 코어 페이지 템플릿 해석 과정의 비인증 로컬 파일 포함 취약점으로, 원격 코드 실행까지 확대 가능하다.
AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N)상태: 제공된 랩에서 입증됨.
WordPress 7.1.1 (Twenty Twelve 테마)은 비인증 공격자가 pagename 쿼리
변수를 통해 테마 디렉터리 외부의 PHP 파일을 포함할 수 있게 하며, 공식 PHP
이미지의 기본 register_argc_argv = On 설정과 함께라면 PEAR pearcmd.php를
통해 마커 파일을 작성하고 실행할 수 있다:
WordPress: 7.1.1
Theme: twentytwelve (page-templates/ present)
PHP: 8.2 (official image, register_argc_argv=On, PEAR present)
Result: LFI oracle fired (<opml version="1.0">); marker probe executed as
uid=33(www-data) and self-deleted
동일한 설정의 WordPress 7.1.2는 오라클을 완전히 차단한다 (음성 대조군). 이는 컨테이너 내부에서 WordPress 웹 서버 사용자 권한의 코드 실행을 입증한다. 루트 접근, 컨테이너 탈출, 측면 이동, 호스트 침해는 입증하지 않는다.
요구사항: Compose v2가 포함된 Docker, Python 3.10+, Docker Hub 및 wordpress.org (테마 설치)에 대한 네트워크 접근.
./lab verify
취약한 랩을 빌드하고 LFI 오라클과 RCE 마커가 발동할 것을 요구한 다음, 패치된 랩을 빌드하고 오라클이 침묵할 것을 요구한다.
다른 루프백 포트를 사용하려면: LAB_PORT=9499 ./lab verify.
이후 모든 것을 제거하려면 ./lab reset.
취약한 대조군:
[+] page-* folder(s) reachable: page-templates (traversal can resolve)
[!!!] LFI CONFIRMED (safe oracle: wp-links-opml.php)
...
[RCE] pearcmd.php reachable and taking argv: /usr/local/lib/php/pearcmd.php
[!!!] RCE CONFIRMED (pearcmd.php config-create file write):
evidence: uid=33(www-data) (command `id` ran as the web user)
cleanup : probe self-deleted on execution: confirmed
RESULT: VULNERABLE CONTROL CONFIRMED
패치된 대조군:
[=] oracles did not fire: target patched (>= 7.1.2), ...
RESULT: PATCHED CONTROL CONFIRMED
HTTP 200, WAF 차단, 또는 오라클 마커 없는 부분적 트래버설은 증거로 인정되지 않는다.
./lab start vulnerable # fresh 7.1.1 lab on 127.0.0.1:9471
./lab test # assert LFI + RCE
./lab start patched # fresh 7.1.2 lab
./lab test # assert oracle absence
./lab status
./lab reset # containers, volumes, state
실행 중인 랩에 대해 도구를 직접 실행:
python3 poc/triage_cve_2026_87902.py --url http://127.0.0.1:9471
python3 poc/poc_cve_2026_87902.py --url http://127.0.0.1:9471 --rce
nuclei -t nuclei/cve-2026-87902.yaml -u http://127.0.0.1:9471 -silent
소유하거나 명시적으로 테스트 권한을 부여받은 시스템(버그 바운티 범위, 서면 계약)의 경우, 드라이버는 명시적 오버라이드를 요구한다:
python3 poc/poc_cve_2026_87902.py --url https://target.example \
--allow-authorized-non-loopback [--insecure] [--rce]
wp-includes/template.php의 get_page_template()은 pagename 쿼리
변수로부터 후보 템플릿 파일명을 구성한다. 세 줄 위의 형제 분기는
페이지 템플릿 슬러그를 validate_file()을 통해 전달하지만, pagename
분기는 그렇게 하지 않았으며, 추가로 urldecode()로 디코딩된 후보를
하나 더 추가한다:
// wp-includes/template.php, get_page_template(), WordPress <= 7.1.1
if ( $pagename ) {
$pagename_decoded = urldecode( $pagename );
if ( $pagename_decoded !== $pagename ) { // 7.1.2 adds validate_file()
$templates[] = "page-{$pagename_decoded}.php";
}
$templates[] = "page-{$pagename}.php";
}
7.1.2는 디코딩된 후보에 validate_file()을 적용하고 추가로 모든
해석된 템플릿 경로가 통과해야 하는 포함 게이트
(_wp_is_template_path_allowed())를 추가한다.
locate_template()은 둘 다 확인)가
page-로 시작하는 최상위 디렉터리를 포함해야 한다. 레거시 기본
테마(Twenty Twelve, Twenty Fourteen)와 여러 wp.org 테마(Neve, Hestia,
Sydney)가 해당된다. 조사된 상용 테마(Divi, Avada, Flatsome,
Woodmart, Betheme, The7, Enfold, Salient, Jupiter/X, Porto, Houzez,
XStore, Kalium, Uncode)는 전혀 포함하지 않는다 - 트래버설이 해석될
수 없다; 미러 소스 목록은 PoC의 PRO_THEME_PAGE_DIRS를 참조..php로 끝나야 한다 (코드에 의해 추가됨).register_argc_argv = On과 PEAR의 pearcmd.php가
필요하다.권고문과 Patchstack의 글은 작동하는 요청을 공개하지 않는다. 이 세부 사항들은 이 랩에서 경험적으로 확립되었다:
page_id 앵커가 필수적이다. 해석되지 않는 pagename은
is_404를 뒤집고, 그 템플릿이 get_page_template()을 완전히
가린다. 해석 가능한 게시된 page_id로 쿼리를 앵커링하라.get_posts()는 pagename을
sanitize_title_for_query( wp_basename( … ) )를 통해 재작성한다:
원시 ..는 -가 되지만 %hex 시퀀스는 살아남는다;
get_page_template()의 추가 urldecode()가 트래버설을 드러낸다.
%252e/%252f만 작동한다.redirect_canonical이 GET을
301 리다이렉트하고 페이로드를 버린다; 비 GET/HEAD에 대해서는 즉시
반환한다).page-templates/로부터 ../ 네 번.$_POST를 쿼리 변수에
병합한다) - WAF 우회 계층의 기반.$argv 토큰을 urldecode하지 않고,
PEAR의 설정 직렬화기는 두 인용 스타일 모두 이스케이프한다 - 따라서
원시적이고 인용 없는 + 구분 프로브.| 경로 | 목적 |
|---|---|
poc/poc_cve_2026_87902.py | 전체 PoC: 버전 + WAF 핑거프린팅, 테마/page-* 폴더 열거 (wp.org SVN, 에셋 참조 발견, 조사된 프로 테마 맵), 앵커 발견, 8가지 변형 LFI 오라클, 비교용 빈 본문 폴백 오라클, --rce pearcmd 체인, --lab docker 데모 |
poc/triage_cve_2026_87902.py | 명명된 실패 게이트와 함께 대상별 또는 배치 판정 (EXPLOITABLE / NOT EXPLOITABLE / WAF SUSPECTED / INCONCLUSIVE) |
poc/bounty_screen.py | 범위 내 버그 바운티 자산 목록을 처리: crt.sh 와일드카드 확장 → WP 필터 → 전제 조건 스크린 → 후보 핸드오프 |
nuclei/cve-2026-87902.yaml | Nuclei 템플릿: 안전한 LFI 오라클 (174개 페이로드: 10개 page-* 나머지, 이중/삼중 인코딩 계층, id 1–30) |
WAF 핑거프린팅은 Imperva/Incapsula, Cloudflare, Akamai, AWS
CloudFront/WAF, Sucuri, F5를 포함한다. 8가지 오라클 변형: 이중 인코딩,
대체 %25%32%65 표기, 파라미터 오염, 삼중 인코딩 - 각각 쿼리
스트링과 POST 본문에서. 멀티파트 본문과 원시 슬래시 페이로드는
테스트되었고 거부되었다 (WP는 이 싱크에 대해 이를 처리하지 않는다).
| 버전 | 평가 |
|---|---|
| WordPress 7.1.1 | LFI + pearcmd RCE 종단 간 재현됨 |
| WordPress 6.5.3 / 5.9.17 | LFI 오라클 재현됨 |
| WordPress 4.7.0–7.1.1 (기타 브랜치) | 영향받는 것으로 보고됨; GHSA별 브랜치 표 |
| WordPress 7.1.2 | 패치된 음성 대조군 재현됨 |
.
├── .github/workflows/validate.yml
├── .gitignore
├── LICENSE
├── README.md
├── SECURITY.md
├── docker-compose.yml
├── lab
├── nuclei/
│ └── cve-2026-87902.yaml
└── poc/
├── bounty_screen.py
├── poc_cve_2026_87902.py
└── triage_cve_2026_87902.py
커밋되지 않음: .lab-state, __pycache__/, 스크리너 출력 파일.
| 주장 | 상태 |
|---|---|
| 테마 디렉터리 외부의 비인증 LFI | 입증됨 (7.1.1, 6.5.3, 5.9.17) |
웹 사용자로서 pearcmd.php config-create를 통한 RCE | 입증됨 (7.1.1 랩) |
| 프로브 자체 삭제 / 지속적 아티팩트 없음 | 입증됨 |
| 패치된 음성 대조군 (7.1.2) | 입증됨 |
| 모든 중간 영향 릴리스 테스트 | 테스트되지 않음 |
| 실제 상용 WAF에 대한 WAF 우회 변형 | 랩에서 도출됨, WAF 테스트되지 않음 |
| 루트 권한 / 컨테이너 탈출 / 호스트 침해 | 주장하지 않음 |
page_id 앵커와 이중 인코딩 요구사항을 확인; 비교용
wp-content/index.php 빈 본문 오라클의 출처로, 강화된 사이트를 위한
폴백으로 여기에 포함됨)취약점은 Robert Ressl에 의해 보고되었다. 이 저장소는 독립적인 제3자 분석 및 도구이다.
MIT - LICENSE 참조.
이 저장소는 소유하거나 명시적으로 테스트 권한을 부여받은 시스템에서만
실행하라. 랩 드라이버는 vulnerable/patched 이외의 변형을 거부하고,
Python 드라이버는 --allow-authorized-non-loopback이 제공되지 않는 한
비루프백 대상을 거부하며, Compose 포트는 127.0.0.1에 바인딩된다.
공개 처리에 대해서는 SECURITY.md를 참조. 이 저장소는 프로덕션 대상, 고객 데이터, 액세스 토큰, 또는 실제 시스템의 증거를 포함하지 않는다.
구체적으로:
127.0.0.1에만 게시한다.wp-links-opml.php를 포함한다.--rce는 옵트인이며, 공개적으로 문서화된 pearcmd.php
config-create 기법을 통해 인용 없는, 자체 삭제 마커 프로브
(<?=id,unlink(__FILE__)?>)를 작성한다; 프로브는 단일 실행 시
스스로를 제거하고 PoC가 삭제를 검증한다. 셸, 콜백, 지속성 없음.start마다 원본 공식 이미지에서 재프로비저닝한다; 대상
계측 없음.