2 ポイント 投稿者 GN⁺ 16 시간 전 | 1件のコメント | WhatsAppで共有
  • 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は、1つのリクエストで複数の仮想APIリクエストを処理する。エンドポイント自体には認証なしでアクセスできるが、各サブリクエストには認証情報が引き継がれる
  • 通常のRESTリクエストは次の順で処理される
    • has_valid_params()で必須値と妥当性を検査する
    • sanitize_params()で値をサニタイズする
    • 権限コールバックを実行する
    • エンドポイントコールバックを実行する
  • Batch APIは性能のため、検証と実行を2つのループに分離している
    • 1つ目のループですべてのリクエストを検証・サニタイズする
    • 2つ目のループで検証結果を確認し、権限およびエンドポイントコールバックを実行する
  • 実装は、ルートマッチング結果の$matchesと検証結果の$validationで同じインデックス同士が対応すると仮定している
  • 不正なリクエストがis_wp_error($single_request)分岐に入ると、$validationには項目を追加するが、continueのため$matchesには追加しない
    • その後、すべての$matches項目が1つずつずれる
    • あるリクエストのパラメータを別のリクエストのルールで検証した後、本来意図されていないエンドポイントハンドラで実行できる
  • このインデックス不一致を使うと、パラメータをサニタイズしない別エンドポイントの検証結果を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は、1つのリクエスト内で繰り返し参照される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の投稿は1つの親を持てるが、自分自身や子孫投稿を親にする循環構造は許可しない
  • wp_insert_post_parentフィルタは親階層をたどって循環を検査し、循環を見つけるとその投稿のpost_parentを0に変えるwp_update_post()を呼び出す
  • この2回目の呼び出しではIDpost_parentだけが指定され、post_contentは上書きされない
  • SQLインジェクションでメモリ上の投稿を、自分自身を親に持つcustomize_changesetとして偽装すると、循環修復の過程で攻撃者指定の悪意あるchangeset JSONがデータベースに書き込まれる
  • 日付が過去で状態がfutureのchangesetを作ると、WordPressがそれを適用し、user_id: 1に従って管理者権限で指定された設定変更を実行する
  • 管理者権限はchangeset処理中だけ維持され、完了後は再びゲスト権限に戻る

動的フックによるリクエスト全体の再実行

  • WordPressのフックはアクションとフィルタに分かれ、プラグインがログイン・投稿・スクリプト登録などライフサイクルの各所に介入できるようにする
  • 投稿状態が変わると、WordPressは"{$new_status}_{$post->post_type}"形式の動的アクションを実行する
    • 通常投稿ならpublish_postのような名前になる
    • メモリ上の偽投稿では状態とタイプを任意に決められるため、アンダースコアを1つ以上含む任意のアクション名を構成できる
  • フック引数は攻撃者が決めた投稿IDとWP_Postオブジェクトに限られるため、任意アクションを直接有用に呼び出すのは難しい
  • 攻撃チェーンでは状態をparse、タイプをrequestに改ざんし、**parse_request**フックを呼び出す
  • parse_requestはリクエストライフサイクルの初期に実行されるフックであるため、これを再度呼ぶと元のBatch APIリクエスト全体が最初から再処理される
  • 再処理はchangesetが設定した一時的な管理者の身元が維持されている間に進むため、最初の実行で権限不足により失敗した管理者専用リクエストが2回目の実行では成功する

2つのリクエストで完成するRCEチェーン

  • 最終エクスプロイトは2つのHTTPリクエストを使い、実際の投稿と衝突しないほど大きいIDを偽投稿に割り当てる
  • 1つ目のリクエスト: 永続行の準備

    • SQLインジェクションで3つのローカル埋め込みを持つ偽投稿を返し、OCDに対応する3つのoembed_cacheを作成する
    • 3つの埋め込みは同じ投稿Sを指すが、異なるクエリ文字列を使って別々のoEmbedキャッシュハッシュを作る
  • 2つ目のリクエスト: 6投稿の組み立て

    • メモリキャッシュ内に次の6つの偽投稿を構成する
    • 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リクエストには最初から新規管理者作成リクエストが含まれている
    • 1回目の処理ではゲスト権限のため失敗する
    • parse_requestで再実行されたときは一時的な管理者権限が残っているため成功する
    • 新しい管理者アカウントでログインした後、バックドアプラグインZIPをアップロードすれば、最終的にリモートコード実行に到達する

AIセキュリティ研究で変わる役割

  • 全チェーンの中で特に創造的な段階は、再帰的なBatch呼び出しでGET制限を回避した点、キャッシュとchangesetを組み合わせて管理者権限を得た点、偽投稿でparse_requestを呼び出してリクエストを再実行した点である
  • 一般的な優位性を断定はできないが、AIなしでセキュリティ研究者が同じチェーンを10時間以内に発見し完成させるのは不可能だっただろうという評価である
  • 元のBatchバグを事前に与えられていたとしても、その時間内にRCEまで構成できたかは確信しにくいとしている
  • 離れた複数のコードガジェットを見つけ、1つのチェーンに結びつける能力で、GPT5.6 Sol Ultraは以前のGPT5.5より大きく進歩したと判断している
  • モデルが技術的なエクスプロイト開発をより多く担うほど、人間は調査対象の製品と攻撃面、投入時間、研究方向を決め、モデルが外れたときに修正する役割へ集中するようになる
  • こうしたメタ研究能力はまだAIがうまく扱えず、モデルの技術力が高まるほど、より重要になると見込まれている

1件のコメント

 
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 の間にはスペースを2つ入れ、INDEX ではなく KEY を使うなど、極端に細かいフォーマット規則を要求している。フィールド名には引用符やバッククォートを使ってはならず、データ型は小文字、SQLキーワードは大文字で、長さパラメータもすべて指定しなければならない
    • WordPress のコードベースは恥ずべきレベルだ。PHPはいまや優れた言語になったが、WordPressはそれをひどく台無しにして使い、改善も拒んでいる
    • 修正方法もひどい。WordPressがいまだに基本的な文字列連結と sprintf でSQLクエリを作っているのか疑問だ
    • 今週末、運用中のサイトの1つでこのエクスプロイトを使った攻撃を見た。
      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はしばしばブログ機能付きのリモート root シェルと呼ばれるほどだ

    • ブログになぜ静的ページで十分でないのか、いまだに理解しがたい。特に WordPress の問題の大半はキャッシュ追加で「解決」している。
      一般ユーザーに GitHub リポジトリへコミットして Hugo でビルドしろと言うより、ドラッグ&ドロップを案内するほうが簡単なのは理解している。だがセキュリティの観点では、コアや何千ものプラグインのどれかに脆弱性が生じ、サービスとしてのリモートコード実行が開くだけを待つ構造だ
    • 事実かどうか知るには脅威インテリジェンスを行い、仲介業者が活動する Telegram グループに潜入する必要があるが、筆者がそうした可能性は低い。通常の脆弱性とゼロデイを混同したのかもしれない
    • WordPressは史上最も多くセキュリティ強化が行われてきた標的の1つだ。古いコードが数十年ほとんど変わっていないため、バグの大半はすでに発見されパッチ済みと見ることもできる
    • インターネット上のウェブサイトのほぼ50%が WordPress を使っているという統計もあるので、未公開の認証不要ゼロデイ・リモートコード実行に50万ドルが付くのは、まったく非現実的というわけではない
  • 興味深い記事であり、LLMベースのエクスプロイト発見と公開は実際に懸念すべき問題だ。Linux のローカル権限昇格脆弱性からコンテナ脱出コードをモデルに比較的短時間で作らせたこともある。
    ただし、GPT-5.6 が安全策でプロンプトをブロックしなかった点は驚きだ。GPT-5.5以上は Opus 4.7+/Fable のように攻撃的セキュリティ作業を嫌がる傾向があるため、筆者が OpenAI から安全策を緩和するサイバーセキュリティ承認を受けていた可能性がありそうだ

  • 2020年以前の非AIの**静的アプリケーションセキュリティテスト(SAST)**ツールでも、この種のSQLインジェクションは多く検出できていたし、少なくともコードレビューでは見つけるべきだった。WordPressがコードレビューやSASTを使っていないのか疑問

    • この攻撃は複数の脆弱性を組み合わせる必要があるため、そのようなツールだけでは検出できなかったはず
  • 私のWebサイトの一つがこの脆弱性でハッキングされたが、幸いユーザーのいないサイトだった
    攻撃者はデータベースに管理者アカウントを2つ作成し、wp-content/plugins/wp-corewp-core-[任意の12文字].phpというリモートコマンド実行Webシェルを設置した。mu-pluginsにはGET ?sergeiで管理者を作るfirewall.phpバックドアを置き、cache-seo-helper.phpバックドアも追加し、fixer.phpでWordPressのバージョン番号をパッチ適用済みバージョンのように書き換えていた。結局、WordPressの利用をやめることにした

  • 記事の最後で投稿を奇妙な名前で呼び始めてから理解しづらくなった。片方のIDをO、もう片方を0にして、EMBED_01ABCDEFの代わりに単一文字とランダムに見えるOCPDSTを使った理由が気になる

    • すべてプレースホルダーで、意味は本文に書かれている。Opublish/oembed_cacheCfuture/customize_changesetPdraft/pageDparse/requestSは埋め込みデータを提供するpublish/postTは外部埋め込みを含むpublish/postを意味する
  • 50万ドルを払う人たちには、GPT-5.6を直接活用する能力がないと仮定しているということなのか疑問

    • それなら、なぜ同じ分析記事がもっと早く出なかったのか考える必要がある。LLMの出力を検証し、実際に有効な概念実証に仕上げるには、依然として専門性が必要
      私もLLMでセキュリティ脆弱性を探しているが、結果をそのまま提出して終わりにはできないし、そうしようとする人は多い
    • 捜査機関がクラウドLLMの記録を入手して刑事訴追の証拠に利用したという記事が相次いでいるが、プロの犯罪者なら、非倫理的ではあっても合法的な仲介者を通じて活動をロンダリングする可能性が高い
    • 稼ぐ人と最高のコードを書く人が必ずしも同じとは限らない。Elon Muskもロケットのコードを自分で書いたのではなく、書く人を雇った
  • GPT-5.6 Solが超人的かは単純なイエス・ノーの質問ではない。コンピューターは数十年前からチェスで人間を上回っていたし、この記事を見ると今やコード理解でも人間を超えたように見える

    • 算術計算では、それよりはるか以前から人間を上回っていた