2P by GN⁺ | ★ favorite | 댓글 1개
  • GPT5.6 Sol Ultra가 최신 안정판 WordPress를 분석해 인증 전 SQL 주입부터 관리자 계정 생성과 원격 코드 실행(RCE)까지 이어지는 공격 체인을 10여 시간 만에 완성함
  • 출발점은 WordPress 5.6부터 제공된 Batch API의 배열 인덱스 불일치로, 재귀 Batch 요청을 결합해 GET 제한과 매개변수 검증을 우회함
  • 검증되지 않은 author_exclude 문자열로 UNION 주입을 일으킨 뒤, 조작한 게시물을 메모리 캐시에 넣고 oEmbed 캐시·changeset·순환 참조·훅을 연쇄적으로 악용함
  • customize_changesetuser_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를 업그레이드할 시간을 제공했으며, 그사이 CalifHacktron이 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 검증을 우회함
  • 0) OR 1=1 -- 같은 값을 넣으면 모든 게시물 행이 반환돼 주입 여부를 확인할 수 있음
  • 이후 UNION 기반 주입으로 wp_posts와 같은 형태의 행을 만들어 임의의 데이터베이스 값을 유출할 수 있음
  • 비밀번호, 재설정 토큰, API 키 등은 데이터베이스에서 해시되므로 관리자 비밀번호가 약하지 않다면 데이터 유출만으로 계정 탈취까지 이어지지는 않음

요청 내 게시물 캐시 조작

  • WordPress는 한 요청 안에서 반복 참조되는 WP_Post 객체를 메모리 캐시에 저장해 데이터베이스 왕복을 줄임
  • UNION 주입으로 가짜 게시물 행을 반환하면 게시물 ID, 타입, 상태, 부모 관계, 본문 등 상당수 필드를 공격자가 정한 상태로 캐시에 넣을 수 있음
  • API 응답에서도 게시물 본문을 후처리하므로, 조작한 본문으로 추가 코드 경로를 실행할 수 있음
  • 이 캐시는 요청이 끝나면 사라지고 가짜 게시물도 데이터베이스에 존재하지 않아, 캐시 조작만으로는 요청 간 지속성을 확보할 수 없음

oEmbed 캐시를 이용한 데이터베이스 행 생성

  • WordPress의 embeds 기능은 게시물 본문의 [embed]...[/embed] 구문으로 지원되는 원격 콘텐츠를 삽입함
  • 매번 HTTP 요청을 보내지 않도록 결과를 wp_postsoembed_cache 타입 게시물로 데이터베이스에 저장
  • 상대 경로로 로컬 WordPress 게시물을 임베드하면 HTTP 요청을 생략하며, 참조한 게시물 ID가 실제로 존재하는지는 확인하지 않음
  • /?p=10 같은 존재하지 않는 로컬 게시물을 임베드해도 해당 데이터용 oembed_cache 행이 만들어질 수 있음
  • 생성된 행을 다시 SQL 주입으로 조회하면서 메모리상의 게시물 타입을 post 등으로 조작하면 데이터베이스와 캐시의 표현이 달라짐
  • WordPress는 두 표현을 조정하면서 wp_update_post()를 호출하고, 명시적으로 지정한 IDpost_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()를 호출함
  • 이 두 번째 호출은 IDpost_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 캐시 해시를 만듦
  • 두 번째 요청: 여섯 게시물 조립

    • 메모리 캐시에 다음 여섯 가짜 게시물을 구성함
    • O: 상태/타입이 publish/oembed_cache이고 부모가 C인 오래된 캐시
    • C: 상태/타입이 future/customize_changeset이며 자신을 부모로 갖고 악성 changeset JSON을 포함함
    • P: 부모가 Ddraft/page
    • D: 상태/타입이 parse/request이며 자신을 부모로 가짐
    • S: 임베드 데이터를 제공하는 publish/post
    • T: 바깥쪽 임베드를 담은 publish/post
    • T의 임베드가 O를 조회하고, 오래된 수정 시각 때문에 S의 캐시를 갱신하게 만듦
    • O 갱신 중 부모 C의 순환을 감지하면 C의 부모를 0으로 바꾸면서 메모리상의 customize_changeset과 악성 JSON을 데이터베이스에 기록함
    • 과거 날짜의 future changeset이 적용되면서 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가 잘 처리하지 못하며, 모델의 기술적 능력이 높아질수록 더 중요해질 것으로 전망함

댓글과 토론

Hacker News 의견들
  • 이런 익스플로잇에 50만 달러가 지급됐거나 지급될 것이라는 근거는 없음. 글에서 프롬프트를 성서처럼 조심스럽게 수정한다고 했으니, 차라리 그 프롬프트를 50만 달러에 팔면 될 듯함
    작성자는 자동 스캐닝 AI 제품을 제공하는 https://www.assetnote.io/에서 일함

    • 아마 https://www.crowdfense.com/exploit-acquisition-program/을 가리키는 듯함. Zerodium도 2021년에 최대 30만 달러를 제시했음: https://www.securityweek.com/sites/default/files/images/Zero...
      이런 중개업체는 보통 거액을 일시불로 지급하지 않고 국가 행위자에게 접근권을 판매한 뒤, 버그가 패치되지 않는 동안 나눠 지급함. 재판매나 조기 소진을 막기 위한 구조라서 비슷한 취약점으로 실제 전액을 받았는지 확인해 줄 사람은 거의 없을 듯함
    • 제목에서 50만 달러를 삭제했음
    • LLM 이전에도 이런 취약점을 찾을 능력과 중개업자에게 팔 의향이 있으면서, 동시에 소셜 미디어에 거대한 체포해 달라는 표지판을 내걸 만큼 어리석은 사람의 교집합은 극히 작을 듯함
      가장 가까운 예는 Steam 게임에 악성코드를 넣어 계정을 털다가 붙잡힌 플로리다 청소년들임. 어차피 잡혔겠지만 소셜 미디어에 과시하지 않았다면 수사에 훨씬 오래 걸렸을 것임
    • 새 제품일 때 5,000달러였던 Macintosh를 누군가 중고 장터에서 25달러에 샀다고 해서 같은 가치 비교가 성립하는 것은 아님
    • 성서처럼 수정한다는 말은, 명백히 모순되거나 윤리적으로 타락했어도 전혀 고치지 않는다는 뜻인가 싶음
  • https://github.com/WordPress/WordPress/commit/3a640e1c5e39aa...을 보면 2026년에도 문자열 연결 SQL 주입이 등장함

    • 더 심각한 내용은 https://developer.wordpress.org/plugins/creating-tables-with...에 있음
      SQL을 직접 실행하는 대신 dbDelta를 쓰라고 하면서, 필드마다 줄을 나누고 PRIMARY KEY 사이에는 공백 두 개를 넣으며 INDEX 대신 KEY를 써야 하는 등 극도로 까다로운 서식 규칙을 요구함. 필드명에는 따옴표나 백틱을 쓰면 안 되고, 자료형은 소문자, SQL 키워드는 대문자여야 하며 길이 매개변수도 모두 지정해야 함
    • WordPress 코드베이스는 수치스러운 수준임. PHP는 이제 훌륭한 언어가 됐지만 WordPress는 이를 심하게 망가뜨려 쓰면서 개선도 거부함
    • 수정 방식도 끔찍함. WordPress가 아직도 기본적인 문자열 연결과 sprintf 로 SQL 질의를 만드는지 의문임
    • 이번 주말 운영 중인 사이트 하나에서 이 익스플로잇을 이용한 공격을 봤음
      POSTGET 요청 안에 /wp/v2/widgets?author_exclude=1%29+AND+1%3D0+UNION+ALL+SELECT... 형태의 페이로드가 포함돼 있었음
    • 프로필에 Principal Software Engineer @ Bluehost, WordPress Core Committer라고 적혀 있는데, 이런 코드라면 정말 ‘principal’이라는 표현이 묘하게 들림
  • FOMO식 글쓰기에는 질렸음. 25달러만으로 발견한 게 아니라 어디를 보고 어떻게 탐색할지 아는 업계 전문지식과 수년간 축적한 자료가 있었던 것임
    도박 같은 서사와 모두가 기회를 놓치고 있다는 환상을 그만 퍼뜨려야 함

    • 이런 글은 성공한 순간만 올리며 삶 전체가 멋진 것처럼 보이는 기사판 Instagram과 비슷해 해로움. 25달러라는 계산에는 수년간의 경력뿐 아니라 수많은 실패도 빠져 있음
    • 비용 계산도 정확하지 않음. 구독 요금제를 통해 보조받은 토큰의 비용이 25달러였을 뿐임
    • 직접 했다면 “무료로 해냈다”고 홍보하지 않았을 텐데, 토큰에 25달러를 썼다는 이유만으로 성과를 바라보는 방식이 달라지는 것도 이상함
  • 놀라운 부분은 이미 알려진 취약점의 높은 가격이며, 사실이 아닐 가능성도 있음. WordPress는 흔히 블로그 기능이 딸린 원격 루트 셸로 불릴 정도임

    • 블로그에 왜 정적 페이지로 충분하지 않은지 아직도 이해하기 어려움. 특히 WordPress 문제 대부분을 캐시 추가로 ‘해결’하고 있음
      일반 사용자에게 GitHub 저장소에 커밋하고 Hugo로 빌드하라고 하는 것보다 끌어다 놓기를 안내하는 편이 쉽다는 점은 이해함. 하지만 보안 관점에서는 코어나 수천 개 플러그인 중 하나에서 취약점이 생겨 서비스형 원격 코드 실행이 열리기만 기다리는 구조임
    • 사실인지 알려면 위협 인텔리전스를 수행하고 중개업자들이 활동하는 Telegram 그룹에 잠입해야 하는데, 작성자가 그렇게 했을 가능성은 낮음. 일반 취약점과 제로데이를 혼동했을 수도 있음
    • WordPress는 역대 가장 많이 보안 강화가 이뤄진 표적 중 하나임. 오래된 코드가 수십 년간 거의 바뀌지 않아 버그 대부분이 이미 발견되고 패치됐다고 볼 수도 있음
    • 인터넷 웹사이트의 거의 50%가 WordPress를 쓴다는 통계도 있으므로, 공개되지 않은 인증 불필요 제로데이 원격 코드 실행에 50만 달러가 붙는 게 아주 비현실적이진 않음
  • 흥미로운 글이며, LLM 기반 익스플로잇 발견과 공개는 실제 우려할 문제임. Linux 로컬 권한 상승 취약점으로부터 컨테이너 탈출 코드를 모델이 비교적 빠르게 만들게 한 적도 있음
    다만 GPT-5.6이 안전장치로 프롬프트를 차단하지 않은 점은 놀라움. GPT-5.5 이상은 Opus 4.7+/Fable처럼 공격적 보안 작업을 꺼리는 편이라, 작성자가 OpenAI에서 안전장치를 완화하는 사이버 보안 승인을 받았을 가능성이 있어 보임

  • 2020년 이전의 비AI 정적 애플리케이션 보안 테스트(SAST) 도구도 이런 SQL 주입을 많이 잡아냈고, 적어도 코드 리뷰에서는 발견했어야 함. WordPress가 코드 리뷰나 SAST를 사용하지 않는지 의문임

    • 이 공격은 여러 취약점을 결합해야 하므로 그런 도구만으로는 잡히지 않았을 것임
  • 내 웹사이트 하나가 이 취약점으로 해킹됐지만 다행히 사용자가 없는 곳이었음
    공격자는 데이터베이스에 관리자 계정 두 개를 만들고, wp-content/plugins/wp-corewp-core-[임의 문자 12개].php 원격 명령 실행 웹 셸을 설치했음. mu-plugins에는 GET ?sergei로 관리자를 만드는 firewall.php 백도어를 두고 cache-seo-helper.php 백도어도 추가했으며, fixer.php로 WordPress 버전 번호를 패치된 버전처럼 바꿨음. 결국 WordPress 사용을 포기하기로 함

  • 글 마지막에 게시물을 이상한 이름으로 부르기 시작하면서 이해하기 어려워졌음. 한 ID는 O, 다른 하나는 0으로 만들고, EMBED_01이나 ABCDEF 대신 단일 문자와 무작위처럼 보이는 OCPDST를 쓴 이유가 궁금함

    • 모두 자리표시자이며 뜻은 본문에 적혀 있음. Opublish/oembed_cache, Cfuture/customize_changeset, Pdraft/page, Dparse/request, S는 임베드 데이터를 제공하는 publish/post, T는 외부 임베드를 담은 publish/post를 뜻함
  • 50만 달러를 낼 사람들이 GPT-5.6을 직접 활용할 능력은 없다고 가정하는 셈인지 의문임

    • 그렇다면 왜 같은 분석 글이 더 일찍 나오지 않았는지 생각해 봐야 함. LLM 출력물을 검토하고 실제로 유효한 개념 증명으로 만드는 데는 여전히 전문성이 필요함
      나도 LLM으로 보안 취약점을 찾지만 결과를 그대로 제출하고 끝낼 수는 없으며, 그렇게 하려는 사람은 많음
    • 수사기관이 클라우드 LLM 기록을 받아 형사 기소 증거로 활용했다는 기사가 계속 나오는데, 전문 범죄자라면 비윤리적이지만 합법적인 중개자를 통해 활동을 세탁할 가능성이 큼
    • 돈을 버는 사람과 최고의 코드를 작성하는 사람은 반드시 같지 않음. Elon Musk도 로켓 코드를 직접 작성한 게 아니라 작성할 사람을 고용했음
  • GPT-5.6 Sol이 초인적인가는 단순한 예·아니요 질문이 아님. 컴퓨터는 수십 년 전부터 체스에서 인간을 능가했고, 이 글을 보면 이제 코드 이해에서도 인간을 넘어선 듯함

    • 산술 계산에서는 그보다 훨씬 오래전부터 인간을 능가했음