AIが書く説明図に id="lead-form" を1つ許しただけで、相談フォームの submit ハンドラが、フォーム本体に登録されなくなりました。画面は普通に表示され、CIも赤くなりませんでした。

この記事でわかること

  • idはページ全体で名前空間を共有するため、要素と属性の許可リスト(allowlist)だけでは衝突を防げません
  • 対策として、説明図のidを fig- で始まる名前に限りました
  • 記事を受け付けるときの検証とは別に、配信されたHTMLもHTMLパーサで検査しています
この記事の目次(6節)
  1. DOM Clobberingとは
  2. ページに要素を差し込めるのは説明図だけ
  3. id="lead-form" で submit ハンドラが登録されなくなった
  4. idに fig- 接頭辞を義務づける
  5. 配信された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属性は、使う説明図がないので許可していません。

対策前 対策後 idの値を制限しない ページ全体で1つの名前空間 説明図(文書順で先) id="lead-form" 相談フォーム(後) id="lead-form" getElementById は先にある説明図を返す submit ハンドラが登録されない 画面は正常、CIも赤くならない fig- 接頭辞を義務づける ページ全体で1つの名前空間 説明図(fig- だけ使える) id="fig-lead-form" 相談フォーム id="lead-form" 名前が重ならない submit ハンドラは本体に登録される main・contact も衝突しない
idはページ全体で1つの名前空間を共有します。説明図のidを fig- で始まる名前に限れば、ページ側のidと重なりません。

図の左右で違うのは、説明図が使える名前だけです。フォームの側は何も変えていません。

属性影響する範囲値の制限
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が書く場合は生成量が多いため、衝突する名前が実際に出てくる機会が増えるだけです。

出典

  1. DOM Clobbering Prevention Cheat Sheet — OWASP Cheat Sheet Series(参照 2026-08-14)
  2. HTML Standard — Named access on the Window object(WHATWG)(参照 2026-09-27)
  3. HTML Standard — Document の名前付きプロパティ(WHATWG)(参照 2026-09-27)

任せる

AI秘書に任せる

問い合わせの受付から請求書の作成までの業務を、AIで自動化します。

できることを見る 相談する(初回無料・30分)