WordPress wp2shell 사전 인증 원격 코드 실행 취약점


요약
WordPress는 2026-07-17에 CVE-2026-60137과 CVE-2026-63030의 수정 버전을 배포했다. CVE-2026-60137은 WP_Query의 SQL 삽입 취약점이다. CVE-2026-63030은 REST API 배치 경로 혼동을 이용해 이 SQL 삽입에 인증 없이 도달하고 원격 코드 실행까지 연결하는 취약점이다.
WordPress 6.8.0부터 6.8.5까지는 CVE-2026-60137의 영향을 받으며 6.8.6에서 수정됐다. 6.9.0부터 6.9.4까지와 7.0.0부터 7.0.1까지는 두 취약점의 영향을 받으며 각각 6.9.5와 7.0.2에서 수정됐다. 6.8 이전 버전은 영향을 받지 않는다.
공개된 내용
| 구분 | 내용 |
|---|---|
| 공개 및 수정일 | 2026-07-17 |
| CVE-2026-60137 | WP_Query의 author__not_in 처리에서 발생하는 SQL 삽입 |
| CVE-2026-63030 | REST API 배치 경로 혼동과 SQL 삽입을 연결한 사전 인증 원격 코드 실행 |
| 영향 버전 | 6.8.0~6.8.5는 SQL 삽입, 6.9.0~6.9.4와 7.0.0~7.0.1은 두 취약점 모두 영향 |
| 수정 버전 | 6.8.6, 6.9.5, 7.0.2 |
| 공격 조건 | REST API 배치 엔드포인트에 인증 없이 접근 가능하며, 원격 코드 실행 단계는 영구 객체 캐시를 사용하지 않는 환경에서 성립 |
| 자동 업데이트 | WordPress가 영향받는 버전에 강제 자동 업데이트를 활성화 |
| Cloudflare 대응 | 2026-07-17 17:03 UTC에 두 취약점용 관리형 방화벽 규칙 배포 |
Cloudflare 규칙은 Cloudflare 프록시와 웹 방화벽을 통과한 요청에만 적용된다. 원본 서버로 직접 들어오는 요청과 차단 동작이 기록 전용으로 변경된 규칙은 보호 대상에서 벗어난다.
기술 분석
취약점의 시작점은 WP_REST_Server가 /wp-json/batch/v1 요청을 검증하고 실행하는 방식이다. class-wp-rest-server.php는 경로와 처리기 정보를 $matches에 저장하고 검증 결과를 $validation에 저장한다. 두 배열은 같은 인덱스를 공유해야 한다.
하위 요청이 WP_Error이면 검증 배열에는 오류가 추가되지만 처리기 배열에는 항목이 추가되지 않는다. 이후 실행 루프가 두 배열을 같은 인덱스로 참조하면서, 한 하위 요청이 다른 하위 요청의 처리기로 전달된다. 공격자는 이를 이용해 느슨한 스키마로 값을 검증한 뒤 다른 경로에서 실행할 수 있다.
배치 API는 일반적으로 GET 하위 요청을 허용하지 않는다. 공개 분석에서는 배치 요청을 중첩하고 경로 혼동을 두 번 일으켜 메서드 검증과 매개변수 검증을 차례로 우회했다. SQL 삽입 지점은 게시물 조회 경로의 author_exclude 매개변수다.
### POST /wp-json/batch/v1
-> 잘못된 하위 요청으로 $matches와 $validation의 인덱스 불일치
-> 중첩 배치 호출로 하위 요청 메서드 검증 우회
-> 다른 경로의 스키마로 author_exclude 검증
-> GET /wp/v2/posts 처리기로 전달
-> author_exclude가 author__not_in으로 매핑
-> WP_Query의 NOT IN 절에서 SQL 삽입
정상 요청에서 author_exclude는 정수 배열이어야 한다. 게시물 제어기는 검증을 마친 값을 WP_Query::get_posts()의 author__not_in으로 전달한다. 기존 구현은 값이 배열일 때만 각 원소에 absint()를 적용했다. 문자열이 들어오면 정수 변환을 건너뛴 채 NOT IN (...) 절에 결합됐다.
if ( ! empty( $query_vars['author__not_in'] ) ) {
if ( is_array( $query_vars['author__not_in'] ) ) {
$query_vars['author__not_in'] = array_unique(
array_map( 'absint', $query_vars['author__not_in'] )
);
}
$author__not_in = implode(
',',
(array) $query_vars['author__not_in']
);
$where .= " AND {$wpdb->posts}.post_author NOT IN ($author__not_in)";
}
경로 혼동으로 배열 검증을 우회하면 공격자가 제어하는 문자열이 이 쿼리 조각에 도달한다. 공개 분석은 이를 이용해 임의의 데이터베이스 값을 wp_posts 행 형태로 반환할 수 있음을 확인했다.
원격 코드 실행 체인은 SQL 삽입으로 만든 가짜 게시물 객체를 요청 단위 캐시에 넣는 단계에서 시작한다. 분석된 체인은 이 객체를 oEmbed 캐시 행과 충돌시키고 wp_update_post()의 상태 조정 동작을 이용해 캐시 값을 데이터베이스에 반영한다. 이어 customize_changeset 게시물과 게시물 부모 순환 처리 경로를 연결해 변경 세트의 사용자 식별자로 관리자 실행 문맥을 얻는다. 이 문맥에서 관리자 계정을 만든 뒤 플러그인 설치 기능을 호출해 PHP 코드를 실행한다.
수정 버전은 배치 요청의 오류 항목에서도 처리기 배열의 인덱스가 검증 배열과 어긋나지 않도록 처리한다. author__not_in 경로도 정수 배열이라는 전제에만 의존하지 않도록 보강됐다. 두 수정은 각각 경로 혼동 진입점과 SQL 삽입 지점을 차단한다.
웹 방화벽 적용 후에도 침해 흔적을 점검한다
웹 방화벽은 알려진 공격 요청을 차단하지만 이미 생성된 관리자 계정, 설치된 플러그인, 변경된 PHP 파일과 활성 세션은 제거하지 않는다. 취약 버전이 외부에 노출됐다면 업데이트 후 데이터베이스와 파일 시스템을 함께 조사한다.
탐지 포인트
네트워크
/wp-json/batch/v1또는rest_route=/batch/v1을 대상으로 한 비정상POST요청requests배열 안에/batch/v1을 다시 호출하는 중첩 배치 요청http://:처럼 URL로 해석할 수 없는 하위 요청 경로- 하나의 배치에서 게시물 삭제 경로와 게시물 조회 경로가 연속해 나타나는 요청
author_exclude가 정수 배열이 아니라 문자열로 전달된 요청author_exclude값에 SQL 연산자, 주석 또는 괄호가 포함된 요청- Cloudflare를 거치지 않고 원본 서버에 도달한 REST API 요청
- 관련 요청 직후 WordPress 서버에서 시작된 외부 연결
엔드포인트
wp-content/plugins아래 새로 생성된 디렉터리와 PHP 파일- 웹 루트, 업로드 디렉터리 또는 활성 테마에 추가된 PHP 파일
- WordPress 코어 파일의 해시 변경
- 웹 서버 계정이 실행한 셸, 인터프리터 또는 내려받기 도구
- REST API 요청 직후 생성되거나 권한이 변경된 파일
서버
- 승인되지 않은 관리자 계정과 관리자 세션
wp_users와wp_usermeta에 추가된 권한 계정wp_posts의 비정상적인oembed_cache또는customize_changeset행- REST API 배치 요청 직후 발생한 플러그인 설치 및 활성화
- 웹 서버 오류 기록의 배치 경로 처리 실패와 SQL 오류
wp-config.php또는 WordPress 인증 키에 대한 비정상 접근
### 비정상 배치 요청
-> wp_posts 또는 관리자 계정 변경
-> 플러그인이나 PHP 파일 생성
-> 웹 서버 프로세스의 자식 프로세스 실행
-> 외부 연결 또는 지속성 설정
HTTP 응답 코드만으로 공격 성공 여부를 판단하지 않는다. 같은 시간대의 데이터베이스 변경, 관리자 세션, 플러그인 설치, 파일 생성과 프로세스 실행을 연결해 확인한다. 공개 자료에는 실제 악용 사례가 제시되지 않았으므로 취약 요청 탐지와 침해 성공 판정은 구분한다.
대응
- WordPress 6.8 계열을 6.8.6 이상으로 업데이트한다.
- WordPress 6.9 계열을 6.9.5 이상으로 업데이트한다.
- WordPress 7.0 계열을 7.0.2 이상으로 업데이트한다.
- 관리 화면이나 명령줄에서 실제 설치 버전을 확인해 자동 업데이트 성공 여부를 검증한다.
- 즉시 업데이트할 수 없으면
/wp-json/batch/v1과rest_route=/batch/v1에 대한 익명 접근을 임시 차단한다. - Cloudflare 환경은 CVE-2026-60137과 CVE-2026-63030 규칙이 차단 동작으로 활성화됐는지 확인한다.
- 원본 서버의 직접 접근을 제한하고 승인된 프록시 주소에서 오는 요청만 허용한다.
- 웹 서버 기록에서 중첩 배치 호출, 잘못된 경로와 비정상
author_exclude값을 검색한다. - WordPress 코어, 플러그인과 테마를 신뢰할 수 있는 배포본과 비교한다.
- 신규 관리자 계정,
customize_changeset행과 플러그인 설치 기록을 확인한다. - 침해 흔적이 있으면 WordPress 인증 키, 관리자 세션과 데이터베이스 자격 증명을 교체한다.
- 임의 코드 실행이 확인된 서버는 신뢰할 수 있는 이미지와 검증된 백업으로 재구축한다.
참고 자료
- WordPress 7.0.2 Release
- WordPress Security Advisory for CVE-2026-60137
- WordPress Security Advisory for CVE-2026-63030
- Cloudflare WAF protects WordPress applications from two high-severity vulnerabilities
- wp2shell: Pre Authentication RCE in WordPress Core
- Technical Analysis of the wp2shell Exploit Chain
- CVE-2026-63030 and CVE-2026-60137: Mitigating a Critical Unauthenticated RCE Chain in WordPress
- New Zealand NCSC Alert for CVE-2026-63030 and CVE-2026-60137