クロスサイトスクリプティング(XSS)

読み:くろすさいとすくりぷてぃんぐ / 英語表記:Cross-Site Scripting

Webアプリケーションが外部から受け取った値を適切に処理せずにページへ埋め込む不備を突き、攻撃者が用意したスクリプトを、そのページを表示した利用者のブラウザで実行させる攻撃である。根本的解決には、文字として表示する値へのエスケープやURLの検証などを出力先に合わせて行う。HTML入力を認める場合は、許可した要素や属性だけを残して無害化する。

イマシルちゃんの解説

掲示板や検索結果のページでは、利用者が入力した文字をページの中に表示することがあるんだ。
ただ、作りに問題があると、入力に混ぜたタグがHTMLの一部として扱われてしまう場合があるの。
すると、攻撃者が仕込んだJavaScriptのプログラムが、そのページを開いた人のブラウザで動いちゃう。
ログイン中の情報を盗まれたり、偽の入力画面を見せられたりすることもあるんだよ。
このように、ほかの人のブラウザで勝手なスクリプトを動かす攻撃をクロスサイトスクリプティング(XSS)というんだ。
大切なのは、入力された文字をただの文字として表示して、ページの命令に化けさせないことだよ。

より深い解説

定義と位置付け

クロスサイトスクリプティング(Cross-Site Scripting:XSS)は、Webアプリケーションの脆弱性を利用する攻撃である。攻撃者が直接ねらうのはWebサーバそのものではなく、そのサイトを閲覧する利用者である。ただし原因は、利用者の端末ではなくWebアプリケーション側の実装にある。IPAの安全なウェブサイトの作り方では、SQLインジェクションなどと並ぶ代表的な脆弱性として扱われている。

名前の「クロスサイト」は、罠のサイトやメールのリンクなど別の場所から、脆弱なサイトへ利用者を送り込む典型的な手口に由来する。後述するように、現在は罠のリンクを使わない種類も含めてXSSと呼ぶ。

危険性の核心は、注入されたスクリプトが「脆弱なサイトのページの一部」としてブラウザに実行される点にある。ブラウザは、スキーム・ホスト・ポート番号の組であるオリジンごとに、ページから読み書きできる範囲を分けている。これがSame Origin Policyである。注入されたスクリプトは脆弱なサイトと同じオリジンで動くため、別のサイトからは本来触れられないCookieやページの内容、そのサイトへの要求にまで手が届く。

攻撃が成立する流れ

検索語をそのまま結果ページに表示するサイトを例に、典型的な流れを示す。

  1. 攻撃者は、脆弱なサイトのURLに、スクリプトを含む値をパラメータとして付けたリンクを用意する。
  2. 利用者がメールや罠のページでそのリンクを開くと、ブラウザは脆弱なサイトへ要求を送る。
  3. サイトは受け取った検索語を加工せずにHTMLへ埋め込み、応答として返す。
  4. ブラウザは応答全体を脆弱なサイトのページとして解釈し、埋め込まれたスクリプトを実行する。
  5. スクリプトは利用者のブラウザの中で、そのサイトの権限を使った処理を行う。

正常な場合と、値がHTMLの一部として解釈される場合を比べると次のようになる。スクリプトの中身は省略している。

要求:/search?q=network
応答:<p>「network」の検索結果</p>

要求:/search?q=<script>(攻撃者のスクリプト)</script>
応答:<p>「<script>(攻撃者のスクリプト)</script>」の検索結果</p>

二つ目の応答では、ブラウザは <script> を表示すべき文字ではなく、スクリプトの開始タグとして扱う。入力が「データ」から「ページの構造」へ変わってしまうことが、インジェクション系の攻撃に共通する原因である。

スクリプトが届く経路による分類

XSSは、注入した値がどの経路で利用者のブラウザまで届くかによって分類される。区別はOWASPのDOMベースXSS対策資料でも説明されている。

種類値の届き方主な発生場所
反射型XSS要求に含まれた値を、サーバがその応答の中へそのまま返す検索結果、エラーメッセージ、確認画面
格納型XSS値をサーバのデータベースなどに保存し、後でほかの利用者へ表示する掲示板、コメント欄、プロフィール、問い合わせ管理画面
DOMベースXSSブラウザ上のJavaScriptが、URLなどから得た値を安全でない方法でページへ書き込むクライアント側で画面を組み立てるページ

反射型は罠のリンクを踏ませる必要があるため、利用者ごとに誘導が必要である。格納型は持続型とも呼ばれ、一度保存されると、そのページを見た利用者すべてが影響を受け得る。管理者だけが見る管理画面に表示される場合は、権限の高い利用者がねらわれることになる。

DOMベースXSSでは、サーバが返すHTMLには問題がなくても、ブラウザ側の処理で脆弱性が生じる。例えばURLの # 以降のフラグメントは、通常サーバへ送られない。そのためフラグメントの値を画面へ書き込むスクリプトに不備があると、サーバのログやサーバ側の検査では攻撃の値を確認できないことがある。

この種類のXSSを防ぐには、外部から来た値を書き込む処理を見直す。HTMLとして解釈する innerHTML などへ値を代入せず、文字列として扱うtextContentを使う。値の入口を「ソース」、危険な書き込み先を「シンク」と呼び、両者の組み合わせを点検する。

起こり得る被害

スクリプトがそのサイトの権限で動くため、被害はサイトの機能と利用者の権限によって異なる。必ずすべてが起きるわけではないが、代表例は次のとおりである。

被害の例起きる理由
Cookieの窃取スクリプトからCookieを読み出し、攻撃者のサーバへ送ることがある
なりすまし盗んだセッションIDを使うセッションハイジャックにつながる
偽の画面の表示正規のURLのまま偽のログインフォームなどを表示し、入力を盗むことがある
利用者の権限での操作スクリプトがそのサイトへ要求を送り、設定変更や投稿を行うことがある

利用者から見ると、アドレスバーには正規のサイトのURLが表示されている。証明書やHTTPSの鍵マークも正規のものである。HTTPSは通信路を守る仕組みであり、正規のサーバが返したページの中身が安全かどうかは保証しない。

似た攻撃との区別

Webアプリケーションへの攻撃は名前が似ているため、「どこで何が意図せず解釈・実行されるのか」で区別する。

攻撃不正な解釈・実行が起きる場所悪用するもの
XSS利用者のブラウザ値がHTMLやスクリプトとして解釈されること
CSRFWebサーバ(正規の要求として受け付ける)ブラウザがCookieを自動で付けて要求を送ること
SQLインジェクションデータベース値がSQL文の一部として解釈されること
HTTPヘッダインジェクション応答を受け取るブラウザや中継装置値が改行を通じてヘッダーの構造に変わること

XSSとCSRFは、どちらも利用者のブラウザを使い、利用者の権限で正規サイトに要求を送らせ得る。違いは、CSRFでは罠のページから要求を送らせるだけで、正規サイトのページ内でスクリプトを動かさない点にある。そのためCSRF対策として、ページに埋め込んだ秘密のトークンを要求に付けさせる方法がある。しかしXSSがあると、同じオリジンで動くスクリプトがそのトークンを読み取れる場合があり、CSRF対策も回避されることがある。

出力する場所に合わせて無害化する

対策の中心は、外部から受け取った値を「ただの文字」として表示させることである。入力時ではなく、HTMLへ書き出す時点で、書き出す場所の文法に合わせて処理する。値の出どころが入力フォームか、データベースか、外部APIかを問わず、ページへ書き出すすべての値を対象にする。

HTMLの本文や属性値では、HTMLとして意味を持つ文字を文字参照に置き換える。主な対応は次のとおりである。

文字置換後置換する理由
<&lt;タグの開始と解釈させない
>&gt;タグの終わりと解釈させない
&&amp;文字参照の開始と解釈させない
"&quot;属性値の終わりと解釈させない
'&#39;一重引用符で囲んだ属性値の終わりと解釈させない

先ほどの例でこの処理を行うと、<script> の山かっこは文字参照に置き換わる。ブラウザはその文字列を画面に表示するだけで、スクリプトとして実行しない。

属性値は必ずダブルクォートで囲んでから処理する。引用符で囲まない属性値では、空白などで属性の外へ抜け出せるからである。

書き出す場所によって必要な処理は異なる。HTML本文用の処理だけでは足りない場所があるため、次の点も合わせて確認する。

書き出す場所確認すること
リンク先URL(href など)http:// と https:// で始まるURLだけを許可し、javascript:スキームなどを拒否する
<script> 要素の中外部から受け取った値でスクリプトの中身を動的に作らない
スタイルシート任意のサイトからスタイルシートを読み込ませない
ブラウザ上のJavaScript値を innerHTML などHTMLとして解釈する処理へ渡さない

これらはIPAのクロスサイト・スクリプティング対策でも根本的な対策として挙げられている。同資料は、応答ヘッダーのContent-Typeに文字コード(charset)を必ず指定することも求めている。指定がないとブラウザが文字の符号化方式を推測し、想定外の方式で解釈されると、文字参照への置換が効かなくなる場合があるためである。

実際の開発では、自動でエスケープするテンプレートエンジンやフレームワークを使うことが多い。その場合も、この処理を無効にして生のHTMLを出力する機能を使う箇所は、個別に安全性を確認する必要がある。

HTMLの入力を認める場合

ブログの本文のように、利用者に一部のHTMLを書かせる機能では、すべてをエスケープすると機能が成り立たない。この場合は、入力されたHTMLを構文解析し、許可した要素と属性だけを残す方法を取る。この処理をサニタイズと呼ぶことがある。

危険な文字列を探して取り除く方法は、取り除いた結果として新しい危険な文字列ができる場合があり、表記の揺れによる回避も起きやすい。禁止するものを列挙するのではなく、認めるものを決める許可リストの考え方で、実績のあるライブラリを使うことが望ましい。

被害を小さくする対策

次の対策は、エスケープの漏れがあった場合に被害を減らすためのものである。IPAはこうした対策を保険的対策と呼び、根本的な対策の代わりにはならないとしている。

対策効果限界
入力値の検証仕様に合わない値を受け付けない自由な文字列を受け付ける項目には効かない
CookieのHttpOnly属性スクリプトからCookieを読み出せなくするスクリプトによる画面の書換えや要求の送信は防げない
CSP実行してよいスクリプトをブラウザに制限させる方針の作り方によっては効果が弱まる
WAF既知の攻撃の特徴を持つ要求を遮断するDOMベースXSSや検査を回避する入力には効かないことがある

この属性はCookieの窃取を難しくするが、XSSそのものを防ぐわけではない。スクリプトが動けば、Cookieを読まなくても利用者のブラウザから正規の要求を送れるからである。

CSP(Content Security Policy)は、応答ヘッダーのContent-Security-Policyで、スクリプトなどをどこから読み込み、実行してよいかをブラウザへ指示する仕組みである。MDNのCSP実装ガイドでは、要求のたびに作るランダムな値(nonce)やハッシュ値を使い、それを持つスクリプトだけを実行させる方針を推奨している。

Content-Security-Policy: script-src 'nonce-(応答ごとのランダムな値)'; object-src 'none'; base-uri 'none'

<script nonce="(同じランダムな値)">(サイトが用意したスクリプト)</script>

注入された <script> 要素には正しい値が付いていないため、ブラウザは実行しない。onclick のようなイベントハンドラ属性に書いたインラインのスクリプトも止まる。nonceやハッシュを使わずに 'unsafe-inline' でインライン実行を広く許可する方針では、XSSを防ぐ効果が弱まる。

既存のサイトにいきなり適用すると、正常なスクリプトまで止まることがある。まずContent-Security-Policy-Report-Onlyヘッダーで違反を報告だけさせ、影響を確認してから適用する。

過去には、ブラウザ内蔵のXSSフィルタを制御する X-XSS-Protection ヘッダーも使われた。現在は非推奨であり、MDNはCSPを使うよう勧めている。HTTPSの利用を強制するStrict-Transport-Security(HSTS)は通信路を守るためのヘッダーで、XSS対策ではない。セキュリティ関係の応答ヘッダーは、名前と目的を対応させて覚える必要がある。

設計・運用での確認

XSSは画面の数だけ発生箇所の候補があるため、開発と運用の両方で確認する。

WAFによる遮断の調整や仮想パッチの考え方は、WAFの記事で扱っている。

午後での問われ方

実際の試験ではここに出る!全1問

ネットワークスペシャリスト

午前Ⅱ

この用語が出てくる記事

↑