FlawOpen

flawopen.com/Teardowns/cve-2026-87902-wordpress-page-template-path-traversal

● CVE-2026-87902 · CVSS 9.2[1] · 심각
보안 연구 · FlawOpen

CVE-2026-87902: WordPress 페이지 템플릿 경로 순회

검증되지 않은 디코딩 페이지 이름이 템플릿 해석에 사용되어 인증 없이 로컬 PHP를 포함할 수 있었습니다. RCE에는 특정 테마 및 PHP 조건이 필요합니다.[1]

💡 알기 쉬운 설명 (ELI5)

책을 꺼내기 전에 요청을 확인하는 사서를 떠올려 보세요. WordPress는 템플릿으로 가는 한 경로를 확인했지만 디코딩된 이름 경로는 놓쳤습니다. 특수 경로 표시는 테마 디렉터리 밖으로 이어질 수 있었습니다. 수정은 이름을 검증하고 허용된 디렉터리 안에 제한합니다.[1], [2], [3]

핵심 개념 및 용어

페이지 템플릿 해석
WordPress는 PHP 후보를 순서대로 만들고 활성 테마에서 첫 일치 파일을 찾습니다.[1], [2]
경로 순회
입력을 검증하지 않으면 .. 같은 요소가 의도한 디렉터리 밖으로 이동할 수 있습니다.[1]
로컬 PHP 포함
서버에 이미 있는 PHP 파일을 로드합니다. RCE에는 추가 조건이 필요합니다.[1]
`validate_file()`
사용 전에 허용되지 않는 경로 요소를 거부하는 WordPress 함수입니다.[3]
CWE-98
CWE-98 — include/require 파일 이름의 부적절한 제어: PHP 파일을 선택하기 전에 입력을 충분히 제한하지 않습니다.[1], [7]

프레임워크 매핑

근본 원인 분석 (Root Cause)

get_page_template()에서 WordPress는 pagename을 디코딩해 page-{name}.php를 추가했지만 취약 버전은 validate_file()을 적용하지 않았습니다. 인접 분기에는 검사가 있었습니다. 따라서 경로가 테마 밖으로 나갈 수 있었습니다. page-* 최상위 디렉터리와 읽을 수 있는 로컬 PHP가 필요하며 RCE에는 추가 PHP/서버 조건이 필요합니다. 7.1.2는 디코딩 값을 검증하고 locate_template()의 경로를 제한합니다. WordPress 권고문은 이 취약점을 CWE-98(PHP include/require에 사용되는 파일 이름의 부적절한 제어)로 분류하며, 발견과 책임 있는 공개의 공로를 Robert Ressl에게 돌립니다.[1], [2], [3], [7]

CVE-2026-87902 도달 조건: 노출 범위와 조건부 RCE

배포 조건1차 출처가 확인한 내용운영자 조치
영향받는 WordPress Core 브랜치GHSA는 영향을 받는 버전을 브랜치별로 나열하며, 범위는 4.7.0–4.7.36부터 7.1.0–7.1.1까지입니다. 해당 수정 버전은 4.7.37(가장 오래된 백포트)부터 7.1.2(현재 브랜치)까지이며, 나열된 각 브랜치마다 별도의 수정 버전이 있습니다.설치된 Core 버전을 확인하고 해당 브랜치의 수정 버전을 적용하세요. 4.7 이전 버전은 지원되는 릴리스로 업그레이드하세요.
활성 테마 구조활성 부모 또는 자식 테마에 이름이 page-로 시작하는 최상위 디렉터리(예: page-templates)가 있어야 합니다.활성 부모 테마와 자식 테마 디렉터리를 모두 확인하세요. 활성 테마를 바꾸면 이 조건을 다시 확인하세요.
읽을 수 있는 로컬 PHP 파일선택된 .php 파일이 활성 테마 디렉터리 밖에 존재하고 웹 서버 계정에서 읽을 수 있어야 합니다.웹 서버가 읽을 수 있는 로컬 PHP 파일을 확인하세요. CVE의 버전 범위만으로 파일 포함이 발생했다고 판단하지 마세요.
조건부 RCE 환경권고문은 추가 PHP/서버 조건이 충족될 때만 RCE를 설명합니다. 예로 register_argc_argv=On일 때의 pearcmd.php, 공식 PHP Docker 이미지, PHP 8.5 이전 버전을 사용하는 기본 cPanel 구성을 듭니다.실제 PHP 런타임과 설정을 평가하세요. 영향받는 WordPress 버전만으로 RCE나 침해가 입증되지는 않습니다.

단계별 공격 실행 흐름

단계 1

입력이 템플릿 선택에 도달

인증되지 않은 요청이 후보 생성에 쓰이는 이름을 제공합니다. 계정이나 클릭은 필요하지 않습니다.[1]

단계 2

다시 디코딩

get_page_template()은 이름을 다시 디코딩하며 취약 버전은 디코딩 후보를 검사하지 않았습니다.[2], [3]

단계 3

검색이 루트를 벗어남

page-* 디렉터리와 읽을 수 있는 PHP가 있으면 테마 밖의 파일을 해석할 수 있습니다.[1]

단계 4

영향은 조건부

RCE에는 PHP/서버 조건이 더 필요합니다. 버전만으로 악용 가능성이나 침해를 단정할 수 없습니다.[1]

소스 코드 비교: 취약한 구현 vs 보안 패치

이 발췌문은 공식 7.1.1과 7.1.2 태그를 비교합니다. 패치는 디코딩 후 값을 검증하며 같은 릴리스는 locate_template()에서 해석 경로도 제한합니다. 이는 upstream 소스 변경이지 완전한 독립 패치나 익스플로잇 시연이 아닙니다.[2], [3]

소스 코드 발췌: wp-includes/template.php — get_page_template()

취약한 구현
if ( $pagename ) {
    $pagename_decoded = urldecode( $pagename );

    if ( $pagename_decoded !== $pagename ) {
        $templates[] = "page-{$pagename_decoded}.php";
    }
}
보안 강화 패치
if ( $pagename ) {
    $pagename_decoded = urldecode( $pagename );

    if ( $pagename_decoded !== $pagename && 0 === validate_file( $pagename_decoded ) ) {
        $templates[] = "page-{$pagename_decoded}.php";
    }
}

엔지니어링 및 시스템 보안 강화 체크리스트

출처