- GPT5.6 Sol Ultra가 최신 안정판 WordPress를 분석해 인증 전 SQL 주입부터 관리자 계정 생성과 원격 코드 실행(RCE)까지 이어지는 공격 체인을 10여 시간 만에 완성함
- 출발점은 WordPress 5.6부터 제공된 Batch API의 배열 인덱스 불일치로, 재귀 Batch 요청을 결합해 GET 제한과 매개변수 검증을 우회함
- 검증되지 않은
author_exclude문자열로 UNION 주입을 일으킨 뒤, 조작한 게시물을 메모리 캐시에 넣고 oEmbed 캐시·changeset·순환 참조·훅을 연쇄적으로 악용함 customize_changeset의user_id: 1로 관리자 권한을 일시 획득하고parse_request훅으로 Batch 요청을 재실행해 새 관리자를 만든 뒤, 백도어 플러그인 ZIP을 업로드해 코드 실행에 도달함- 월 200달러 구독의 주간 사용량 50%를 비례 계산한 비용은 약 25달러였으며, 사람의 역할은 제품과 공격 표면 선정, 프롬프트 조정 같은 상위 수준의 연구 지휘로 이동할 가능성이 커짐
발견 과정과 실험 조건
- OpenAI가 Cycle Double Cover 추측 해결에 사용했다고 공개한 프롬프트를 보안 연구용으로 수정해 GPT5.6 Sol Ultra에 제공함
- 최신 안정판 WordPress를
main/에 복제하고.git디렉터리를 삭제했으며, 종속 코드를 조사할 수 있도록 빈third_party/디렉터리를 준비함 - 프롬프트는 최대 4개 에이전트를 병렬로 사용해 최소 6시간 동안 다양한 공격 경로를 유지하도록 지시함
- 입력 파싱, 문자 집합, 파일 업로드, 오류 처리, 내장 경로, 직렬화, 캐시, 경쟁 조건, 암호화, 타입, 대량 할당 등을 탐색함
- 접근법 계열을 등록하고 특정 전략으로 에이전트가 몰리면 덜 탐색된 영역으로 재배치함
- 구체적인 버그는 적대적 에이전트가 재검증하고, 실패한 경로도 새로운 메커니즘이 생기면 다시 열도록 함
- 변경 이력이나 패치 버전과의 차이, 인터넷 단서를 이용하지 않고 소스 코드 자체에서 신규 취약점을 찾도록 제한함
- 비현실적인 설정이나 공격자가 충족할 수 없는 전제 조건을 피하고자 목표를 “MySQL을 쓰는 일반적인 프로덕션 배포에서 인증 전 RCE”로 명시함
- 약 6시간 뒤 모델이 인증 전 SQL 주입을 찾았고, 기본 WordPress 원격 서버에서 관리자 이메일을 수분 안에 추출해 재현함
- RCE로의 승격을 추가 요청하자 약 4시간 뒤, 비밀번호 크래킹이나 오프라인 계산 없이 읽기 전용 SQL 주입에서 관리자 권한으로 올라가는 체인을 완성함
- 총 작업 시간은 10시간 남짓이며, 주간 사용량의 50%를 소모함. 월 200달러 구독료를 비례 계산한 비용은 약 25달러였음
- 공개 전 주말 동안 운영자가 WordPress를 업그레이드할 시간을 제공했으며, 그사이 Calif와 Hacktron이 GitHub의 다른 PoC보다 먼저 전체 체인을 독립적으로 재현함
- 운영 중인 인스턴스는 wp2shell.com에서 취약 여부를 확인할 수 있음
Batch API의 검증 불일치
- WordPress 5.6에서 도입된 Batch API는 하나의 요청으로 여러 가상 API 요청을 처리함. 엔드포인트 자체에는 인증 없이 접근할 수 있지만, 각 하위 요청에는 인증 정보가 전달됨
- 일반 REST 요청은 다음 순서로 처리됨
has_valid_params()로 필수 값과 유효성을 검사함sanitize_params()로 값을 정제함- 권한 콜백을 실행함
- 엔드포인트 콜백을 실행함
- Batch API는 성능을 위해 검증과 실행을 두 개의 반복문으로 분리함
- 첫 번째 반복문에서 모든 요청을 검증·정제함
- 두 번째 반복문에서 검증 결과를 확인하고 권한 및 엔드포인트 콜백을 실행함
- 구현은 라우트 매칭 결과인
$matches와 검증 결과인$validation에서 같은 인덱스끼리 대응한다고 가정함 - 잘못된 요청이
is_wp_error($single_request)분기로 들어가면$validation에는 항목을 추가하지만,continue때문에$matches에는 추가하지 않음- 이후 모든
$matches항목이 한 칸씩 밀림 - 한 요청의 매개변수를 다른 요청의 규칙으로 검증한 뒤, 원래 의도하지 않은 엔드포인트 핸들러에서 실행할 수 있음
- 이후 모든
- 이 인덱스 불일치를 이용하면 매개변수를 정제하지 않는 다른 엔드포인트의 검증 결과를 Batch 지원 엔드포인트에 적용할 수 있음
author__not_in에서 발생하는 SQL 주입
GET /wp/v2/posts는 특정 작성자를 결과에서 제외하기 위해 내부 쿼리 변수author__not_in을 사용함- 값이 배열이면 각 원소에
absint를 적용해 정수로 만들지만, 스칼라 값이면 그대로implode한 뒤 SQL의NOT IN절에 삽입함 - 정상 호출에서는 공개 매개변수
author_exclude가 정수 배열이어야 하므로 문제가 드러나지 않음 - Batch 인덱스 불일치를 이용하면 문자열
author_exclude를 이를 인식하지 않는DELETE /wp/v2/posts/1규칙으로 검증한 뒤GET /wp/v2/posts에 전달할 수 있음 - Batch API가 GET 하위 요청을 허용하지 않는 제약도 재귀 Batch 호출로 우회함
- 바깥쪽 Batch에서 인덱스 불일치를 일으켜 내부 요청의
method검증을 건너뜀 - 안쪽 Batch에서 다시 인덱스 불일치를 일으켜
author_exclude검증을 우회함
- 바깥쪽 Batch에서 인덱스 불일치를 일으켜 내부 요청의
0) OR 1=1 --같은 값을 넣으면 모든 게시물 행이 반환돼 주입 여부를 확인할 수 있음- 이후 UNION 기반 주입으로
wp_posts와 같은 형태의 행을 만들어 임의의 데이터베이스 값을 유출할 수 있음 - 비밀번호, 재설정 토큰, API 키 등은 데이터베이스에서 해시되므로 관리자 비밀번호가 약하지 않다면 데이터 유출만으로 계정 탈취까지 이어지지는 않음
요청 내 게시물 캐시 조작
- WordPress는 한 요청 안에서 반복 참조되는
WP_Post객체를 메모리 캐시에 저장해 데이터베이스 왕복을 줄임 - UNION 주입으로 가짜 게시물 행을 반환하면 게시물 ID, 타입, 상태, 부모 관계, 본문 등 상당수 필드를 공격자가 정한 상태로 캐시에 넣을 수 있음
- API 응답에서도 게시물 본문을 후처리하므로, 조작한 본문으로 추가 코드 경로를 실행할 수 있음
- 이 캐시는 요청이 끝나면 사라지고 가짜 게시물도 데이터베이스에 존재하지 않아, 캐시 조작만으로는 요청 간 지속성을 확보할 수 없음
oEmbed 캐시를 이용한 데이터베이스 행 생성
- WordPress의 embeds 기능은 게시물 본문의
[embed]...[/embed]구문으로 지원되는 원격 콘텐츠를 삽입함 - 매번 HTTP 요청을 보내지 않도록 결과를
wp_posts의oembed_cache타입 게시물로 데이터베이스에 저장함 - 상대 경로로 로컬 WordPress 게시물을 임베드하면 HTTP 요청을 생략하며, 참조한 게시물 ID가 실제로 존재하는지는 확인하지 않음
/?p=10같은 존재하지 않는 로컬 게시물을 임베드해도 해당 데이터용oembed_cache행이 만들어질 수 있음- 생성된 행을 다시 SQL 주입으로 조회하면서 메모리상의 게시물 타입을
post등으로 조작하면 데이터베이스와 캐시의 표현이 달라짐 - WordPress는 두 표현을 조정하면서
wp_update_post()를 호출하고, 명시적으로 지정한ID와post_content외 필드는 공격자가 만든 메모리 값을 선호함 - 이에 따라
oembed_cache행을 일반 게시물로 바꿀 수 있지만, 이 호출에서는post_content가 임베드 결과로 덮이므로 공격자가 본문까지 제어하지는 못함
customize_changeset을 통한 임시 관리자 권한
- 테마 사용자 정의 작업의 초안은
wp_posts에서customize_changeset타입의 특수 게시물로 저장됨 post_content에는 설정 키별 변경값과 타입, 변경을 수행할 사용자 ID가 담긴 JSON이 들어감- changeset을 적용할 때 WordPress는 각 항목의
user_id를 읽어wp_set_current_user()로 현재 사용자를 일시적으로 변경함 - 공격자가
user_id: 1인 changeset을 적용하면 익명 요청 중에도 관리자 신원을 잠시 사용할 수 있음 - 앞선 oEmbed 조정 호출은
post_content를 덮어쓰므로, 악성 changeset JSON을 보존할 별도의wp_update_post()호출 경로가 필요함
게시물 부모 순환을 이용한 본문 보존
- WordPress 게시물은 하나의 부모를 가질 수 있지만, 자신이나 하위 게시물을 부모로 두는 순환 구조는 허용하지 않음
wp_insert_post_parent필터는 부모 계층을 따라가며 순환을 검사하고, 순환을 발견하면 해당 게시물의post_parent를 0으로 바꾸는wp_update_post()를 호출함- 이 두 번째 호출은
ID와post_parent만 지정하고post_content는 덮어쓰지 않음 - SQL 주입으로 메모리상의 게시물을 자기 자신을 부모로 갖는
customize_changeset으로 꾸미면, 순환 복구 과정에서 공격자가 지정한 악성 changeset JSON이 데이터베이스에 기록됨 - 과거 날짜이면서 상태가
future인 changeset을 만들면 WordPress가 이를 적용하고,user_id: 1에 따라 관리자 권한으로 지정된 설정 변경을 수행함 - 관리자 권한은 changeset 작업 중에만 유지되며 완료 후에는 다시 게스트 권한으로 돌아감
동적 훅으로 요청 전체 재실행
- WordPress 훅은 동작(action)과 필터(filter)로 나뉘며, 플러그인이 로그인·게시·스크립트 등록 등 생명주기의 여러 지점에 개입하게 함
- 게시물 상태가 바뀌면 WordPress는
"{$new_status}_{$post->post_type}"형태의 동적 동작을 실행함- 정상 게시물이라면
publish_post같은 이름이 됨 - 메모리의 가짜 게시물은 상태와 타입을 임의로 정할 수 있어 밑줄이 하나 이상 들어가는 원하는 동작 이름을 구성할 수 있음
- 정상 게시물이라면
- 훅 인수는 공격자가 정한 게시물 ID와
WP_Post객체로 제한되므로, 임의 동작을 직접 유용하게 호출하기는 어려움 - 공격 체인은 상태를
parse, 타입을request로 조작해parse_request훅을 호출함 parse_request는 요청 생명주기 초기에 실행되는 훅이므로, 이를 다시 호출하면 원래 Batch API 요청 전체가 처음부터 재처리됨- 재처리는 changeset이 설정한 임시 관리자 신원이 유지되는 동안 진행돼, 첫 실행에서 권한 부족으로 실패했던 관리자 전용 요청이 두 번째 실행에서는 성공함
두 요청으로 완성되는 RCE 체인
- 최종 익스플로잇은 두 개의 HTTP 요청을 사용하며, 실제 게시물과 충돌하지 않을 만큼 큰 ID를 가짜 게시물에 할당함
-
첫 번째 요청: 지속 행 준비
- SQL 주입으로 세 개의 로컬 임베드를 가진 가짜 게시물을 반환해
O,C,D에 대응하는 3개oembed_cache행을 생성함 - 세 임베드는 같은 게시물
S를 가리키지만 서로 다른 쿼리 문자열을 사용해 별도의 oEmbed 캐시 해시를 만듦
- SQL 주입으로 세 개의 로컬 임베드를 가진 가짜 게시물을 반환해
-
두 번째 요청: 여섯 게시물 조립
- 메모리 캐시에 다음 여섯 가짜 게시물을 구성함
O: 상태/타입이publish/oembed_cache이고 부모가C인 오래된 캐시C: 상태/타입이future/customize_changeset이며 자신을 부모로 갖고 악성 changeset JSON을 포함함P: 부모가D인draft/pageD: 상태/타입이parse/request이며 자신을 부모로 가짐S: 임베드 데이터를 제공하는publish/postT: 바깥쪽 임베드를 담은publish/postT의 임베드가O를 조회하고, 오래된 수정 시각 때문에S의 캐시를 갱신하게 만듦O갱신 중 부모C의 순환을 감지하면C의 부모를 0으로 바꾸면서 메모리상의customize_changeset과 악성 JSON을 데이터베이스에 기록함- 과거 날짜의
futurechangeset이 적용되면서user_id: 1의 관리자 신원으로P를 게시함 P갱신은 부모D의 순환을 발견해D를 기록하고, 조작된 상태와 타입으로parse_request동작을 호출함- Batch 요청에는 처음부터 새 관리자 생성 요청이 포함됨
- 첫 번째 처리에서는 게스트 권한이므로 실패함
parse_request로 재실행될 때는 임시 관리자 권한이 남아 있어 성공함- 새 관리자 계정으로 로그인한 뒤 백도어 플러그인 ZIP을 업로드하면 최종적으로 원격 코드 실행에 도달함
AI 보안 연구에서 달라지는 역할
- 전체 체인에서 특히 창의적인 단계는 재귀 Batch 호출로 GET 제한을 우회한 점, 캐시와 changeset을 결합해 관리자 권한을 얻은 점, 가짜 게시물로
parse_request를 호출해 요청을 재실행한 점임 - 일반적인 우월성을 단정할 수는 없지만, AI 없이 보안 연구자가 같은 체인을 10시간 안에 발견하고 완성하기는 불가능했을 것이라는 평가임
- 원래 Batch 버그를 미리 제공하더라도 그 시간 안에 RCE까지 구성할 수 있을지는 확신하기 어렵다고 봄
- 서로 떨어진 여러 코드 가젯을 찾아 하나의 체인으로 연결하는 능력에서 GPT5.6 Sol Ultra가 이전 GPT5.5보다 크게 발전했다고 판단함
- 모델이 기술적 익스플로잇 개발을 더 많이 담당할수록 사람은 조사할 제품과 공격 표면, 투입 시간, 연구 방향을 정하고 모델이 빗나갈 때 교정하는 역할에 집중하게 됨
- 이러한 메타 연구 역량은 아직 AI가 잘 처리하지 못하며, 모델의 기술적 능력이 높아질수록 더 중요해질 것으로 전망함