id属性はallowlistで防げない — DOM Clobberingという名前空間の危険
要素と属性をallowlistで絞っても、id属性だけは別扱いが要る。OWASPが定義するDOM Clobberingの仕組みと、AIが書く説明図に「よくある名前」のidを許してしまい、問い合わせフォームの送信処理が本物のフォームに付かなくなった実例を、実測とともに整理する。
DOM Clobberingとは
DOM Clobberingとは、id属性やname属性を持つHTML要素を注入し、その名前でグローバルな変数やAPIを上書きしてしまう攻撃手法である。
OWASPのチートシートは仕組みをこう説明する。ブラウザはページを読み込むとき、id属性やname属性を持つ要素に対して、windowおよびdocumentオブジェクトの同名プロパティを自動生成する。<form id="x"> と書くだけで document.x や window.x がその要素を指すようになる。攻撃者向けの解説では悪意ある注入が前提だが、この記事で扱う失敗に悪意は要らない。allowlistで要素と属性を絞っていても、id自体の「値」を制限していなければ同じ穴が開く。
説明図の中身をAIに書かせる、という設計
本システムは医療×AIのメディアを自律運転しており、記事本文の中に構造を示す説明図(インラインSVG)をAIが書く経路を持つ。この図は本文と一緒にHTMLへそのまま展開される — 記事の他の部分はすべて文字列としてエスケープされるので、図だけが公開ページのDOMに要素を直接注入できる唯一の経路である。
script要素や外部参照(use・image)を使えないよう要素と属性をallowlistで絞る設計はしていた。問題は、許可したrectやtextのid属性そのものには値の制限を掛けていなかったことである。
id="lead-form" が起こしたこと
図の中に <rect id="lead-form"> を置く注入を実際に試したところ、公開ページの相談フォーム送信処理が起動しなくなった。原因は単純である。計測用のスクリプトは getElementById("lead-form") でフォーム要素を取得して送信ハンドラを紐付けているが、getElementById は文書順で最初に一致した要素を返す。図が本文中でフォームより先に置かれていれば、返るのは図のrectであり、本物の <form id="lead-form"> には一度もハンドラが付かない。フォームに action 属性が無いため、送信は素のGETとしてページ自身に飛び、問い合わせを受け取るエンドポイントは一度も呼ばれない。画面は普通に表示され、CIも赤くならない。
悪意のある文字列を選ぶ必要すらない。id="main"(本文へのスキップリンクの飛び先)や id="contact"(CTAのアンカー)は、図の説明として書いていて自然に選びうる名前である。
対処: 値を許可された部分集合に閉じ込める
要素と属性のallowlistは「その要素の中で完結する危険」しか表現できない。idはページ全体で名前空間を共有するので、別の柵が要る。今回とった対処は、図のidに fig- 接頭辞を強制し、英小文字・数字・ハイフンのみを許可することだった。fig-lead-form は許されても lead-form 単体は拒否される。同じ理由でclass属性も許可していない。
| 属性 | 完結する範囲 | 別扱いが必要か |
|---|---|---|
fill stroke などの見た目 | 要素の中だけ | 不要 |
class | ページ全体のCSS名前空間 | 必要 |
id | ページ全体のDOM名前空間 | 必要 |
入口を塞いだだけでは終わらない
入口(記事を受け付ける検証)を修正したあと、その検証を丸ごと無効化した状態で危険な図を配信物に混ぜ、公開ページ側の別の検査が独立に赤くなることを確認した。同じ弱点が別の経路から入っても検出できるようにするためである。この出口側の検査は、最初は正規表現で id="…" を拾う実装だったため、二重引用符のidしか検出できず、id='lead-form'(単引用符)に変えるだけで素通りした。実際のブラウザの解析結果を確かめると、id='x' と id=x と ID="x" はいずれも同じidとして解釈され、同じ属性が2回書かれていれば最初の値が採用される。この挙動に検査を合わせるため、正規表現ではなく実際のHTMLパーサに読ませる実装に直した。
FAQ
Q. class属性も同じ危険がありますか。 JavaScriptからgetElementsByClassNameやdocument.className経由で参照される設計であれば同様の危険があります。本システムでは説明図のclass属性自体を許可していません。
Q. allowlistを強化すれば防げませんか。 要素や属性の種類を絞るallowlistは有効ですが、それだけでは防げません。idのように値がページ全体で名前空間を共有する属性は、値の形そのものを制限する必要があります。
Q. 入口の検証だけで十分ではないのですか。 不十分です。入口は単一の実装に依存するため、そこにバグがあれば何も残りません。配信された実物のHTMLを独立に検査する出口側の柵が、まだ知らない入口の抜け道も塞ぎます。
Q. この問題はAIが書くコンテンツ特有ですか。 いいえ。ユーザー投稿・CMSのリッチテキスト・SVGアップロードなど、外部からHTML断片を受け入れる設計全般に当てはまります。AIが書く経路は人手より生成量が多いため、抜け道が実際に踏まれる確率が上がるだけです。
外部から受け付けるコンテンツにHTML相当の構造を残す設計は、便利さと引き換えに名前空間の管理が必要になります。AIが生成するコンテンツ基盤の安全設計を検討したい場合は、[ご相談ください](/contact)。