AIが書く説明図に id="lead-form" を1つ許しただけで、相談フォームの submit ハンドラが、フォーム本体に登録されなくなりました。画面は普通に表示され、CIも赤くなりませんでした。
この記事でわかること
- idはページ全体で名前空間を共有するため、要素と属性の許可リスト(allowlist)だけでは衝突を防げません
- 対策として、説明図のidを
fig-で始まる名前に限りました - 記事を受け付けるときの検証とは別に、配信されたHTMLもHTMLパーサで検査しています
この記事の目次(6節)
DOM Clobberingとは
DOM Clobberingは、id属性やname属性を持つ要素を注入し、同名の変数やAPIを上書きする攻撃です。1 ブラウザは、idを持つ要素と、name属性を持つ一部の要素について、windowに同名のプロパティを作ります。documentに作られるのは、主にname属性を持つ一部の要素です。<form name="x"> と書けば document.x と window.x の両方が、<form id="x"> なら window.x がその要素を指すようになります。23
一般の解説は、攻撃者による注入を前提にしています。しかし今回の不具合は、悪意がなくても起きました。
ページに要素を差し込めるのは説明図だけ
医療×AIのメディアを自律運用しているこのシステムでは、AIが記事の中に説明図(インラインSVG)を書きます。記事のほかの部分はすべて文字列としてエスケープされますが、説明図だけはHTMLとしてそのまま埋め込まれます。
script要素や外部参照(use・image)は、許可リストから外していました。問題は、許可した rect や text のid属性の値を、制限していなかったことです。
id="lead-form" で submit ハンドラが登録されなくなった
検証のために説明図へ <rect id="lead-form"> を入れると、相談フォームの送信が受け付けられなくなりました。 計測用のスクリプトは、getElementById("lead-form") でフォーム要素を取得し、submit ハンドラを登録しています。getElementById は、文書順で最初に一致した要素を返します。文書順で説明図がフォームより前にあれば、返るのは説明図のrectです。
フォーム本体には、submit ハンドラが一度も登録されません。ハンドラがないので送信は止められず、action 属性もないため、同じURLへのGETになります。問い合わせを受け取るエンドポイントは呼ばれません。
id="main"(本文へのスキップリンクの飛び先)や id="contact"(「ご相談」リンクの飛び先)も、説明図を書くときに普通に付けそうな名前です。
例えば、お知らせをCMSで書いている会社のサイトで、担当者が見出しに id="contact" と付けたとします。その記事がページ下の問い合わせ欄より先にあれば、「お問い合わせ」のリンクが記事の見出しへ飛ぶおそれがあります。
idに fig- 接頭辞を義務づける
要素と属性の許可リストで防げるのは、要素の中で完結する危険に限られます。そこで、説明図のidには fig- 接頭辞を義務づけ、使える文字を英小文字・数字・ハイフンに限りました。fig-lead-form は通り、lead-form は拒否されます。class属性は、使う説明図がないので許可していません。
図の左右で違うのは、説明図が使える名前だけです。フォームの側は何も変えていません。
| 属性 | 影響する範囲 | 値の制限 |
|---|---|---|
fill stroke などの見た目 | 要素の中だけ | 不要 |
class | ページ全体のCSSの名前空間 | 必要 |
id | ページ全体のDOMの名前空間 | 必要 |
「影響する範囲」がページ全体に及ぶ属性は、許可リストに載せるかどうかとは別に、値の形式まで制限する必要があります。
配信されたHTMLもHTMLパーサで検査する
記事を受け付けるときの検証(入口)を直したあと、その検証を丸ごと無効にして、危険な説明図を配信されるHTMLに混ぜました。別の経路から同じ弱点が入る場合に備え、公開ページ側の検査だけでも検出できる(テストが落ちる)ことを確かめました。
この検査は、最初は正規表現で id="…" を拾っていました。そのため二重引用符のidしか検出できず、id='lead-form'(単引用符)に変えるだけで素通りしました。ブラウザは、id='x' も id=x も ID="x" も、いずれも同じidとして解釈します。同じ属性が2回あれば、最初の値を採用します。
ブラウザと同じ解釈にするため、HTMLパーサで読む実装に改めました。配信されたHTMLを読む検査の作り方は、構造化データと本文の一致を保つ検査でも取り上げています。
よくある質問
Q. class属性も同じ危険がありますか。
JavaScriptがclass名で要素を探す設計(getElementsByClassName など)なら、同じような衝突が起きます。このシステムでは、説明図のclass属性そのものを許可していません。
Q. この問題はAIが書くコンテンツ特有ですか。
いいえ。利用者の投稿・CMSのリッチテキスト・SVGのアップロードなど、外からHTMLの断片を受け入れる設計全般に当てはまります。AIが書く場合は生成量が多いため、衝突する名前が実際に出てくる機会が増えるだけです。
出典
- DOM Clobbering Prevention Cheat Sheet — OWASP Cheat Sheet Series(参照 2026-08-14)
- HTML Standard — Named access on the Window object(WHATWG)(参照 2026-09-27)
- HTML Standard — Document の名前付きプロパティ(WHATWG)(参照 2026-09-27)

