見出し画像

令和5年度 秋期 情報処理安全確保支援士試験 午後 問1|消えたレビューが暴いた蓄積型XSS—情報処理安全確保支援士で学ぶセッションID窃取の実例

この記事で扱うセキュリティ事例

  • 試験区分: 情報処理安全確保支援士

  • 出典: 令和5年度 秋期 情報処理安全確保支援士試験 午後 問1

  • 主なテーマ: 蓄積型(格納型)XSS、出力時のエスケープ処理、入力文字数制限の回避、XHRによるトークン取得、セッションID窃取、なりすまし、SameSite cookie

この記事は「情報処理安全確保支援士 午後問題 全70問」シリーズの1本です。→ 全70問の一覧・テーマ別索引はこちら

導入: なぜこの事例が重要なのか

「商品レビューが16件あるはずなのに、2件しか表示されない」——。利用者からのこの一言は、一見すると単なる表示バグの問合せに見えます。ところが、その裏で進行していたのは、ログイン中の会員のセッションIDが静かに盗み出され、なりすましまで成立しうるという深刻なインシデントでした。

この記事で扱うのは、令和5年度 秋期の情報処理安全確保支援士試験 午後 問1で出題されたWebアプリケーションのセキュリティ事例です。題材は、洋服のECサイトに追加された「商品レビュー機能」。レビュータイトル欄という、ごくありふれた入力欄に攻撃者がスクリプトを仕込み、それを閲覧した会員のブラウザ上で実行させる——典型的なクロスサイトスクリプティング(XSS)攻撃です。

しかも今回の攻撃は手が込んでいます。レビュータイトルには50字という入力文字数制限がありました。普通に考えれば、長いスクリプトを埋め込むことはできません。にもかかわらず、攻撃者は15件もの投稿を分割して使い、HTMLのコメントアウトという仕掛けで、最終的に1本の意味のあるスクリプトを成立させてしまいます。

この記事を読むと、蓄積型XSSがどのように仕込まれ、なぜ閲覧者のブラウザで実行されてしまうのか、攻撃スクリプトがどうやってトークンを取得しセッションIDを窃取するのか、そしてなぜ「攻撃者自身のサイトに同じスクリプトを置いても攻撃は成功しない」のか、その理由までが順を追って見えてきます。受験対策として頻出のXSSと出力エスケープの論点を押さえつつ、Webサービスを運営する経営者にとっても「入力監視とセッション管理をどこまでやるべきか」を考える教材になるはずです。

インシデントの概要

舞台は、洋服のEC事業を手掛ける従業員100名の会社、Q社です。Q社は「Webアプリ Q」と呼ぶWebアプリケーションプログラム(Web上で動くサービスを実現するソフトウェア)でECサイトを運営していました。ECサイトのドメイン名は「□□□.co.jp」で、利用者はWebアプリ QにHTTPS(通信内容を暗号化して盗聴や改ざんから守る方式)でアクセスします。開発も運用もQ社の開発部が担っていました。

Webアプリ Qには、会員登録、ログイン、カート、商品購入、そして会員プロフィールといった機能が備わっていました。ログイン機能では、会員IDとパスワードで会員を認証し、ログインに成功した会員にはセッションID(その会員のログイン状態を識別するための一時的な番号)をcookie(WebブラウザがWebサイトごとに保存する小さなデータ)として払い出します。以後、ブラウザは自動的にこのcookieをサーバへ送り、サーバは「この通信は誰のものか」を見分けます。

会員プロフィール機能には、アイコン画像をアップロードする仕組みがありました。このアップロードは「https://□□□.co.jp/user/upload」に対して、画像ファイル(パラメータ名 uploadfile)と、「https://□□□.co.jp/user/profile」にアクセスして払い出されたトークン(パラメータ名 token。一度限り有効な引換券のような値)の2つをパラメータとして送ることで実行されます。トークンが、profileページで払い出されたものと一致したときにアップロードが成功し、アップロードしたアイコン画像は、プロフィール設定ページやレビューページに表示される——この素朴な仕様が、後に攻撃の踏み台になります。

そして今回、Webアプリ Qに新しく「商品レビュー機能」が追加されました。会員はレビューを投稿でき、その内容は誰でも閲覧できます。会員がレビューページに入力できる項目のうち、レビュータイトルは50字、レビュー詳細は300字という入力文字数制限が設けられていました。一見、なんの変哲もないレビュー機能。問題は、この入力された文字がどのように画面に出力されるかにありました。

何が起きたのか

何が起きたのか

ある日、Q社に一件の問合せが寄せられます。「無地Tシャツのレビューページ(以下、ページV)に、16件表示されるはずのレビューが2件しか表示されていない」。

対応にあたったのは、開発部のリーダーであるNさんです。Nさんが実際にページVを閲覧してみると、画面遷移のうえでおかしな点はありません。商品レビューの見出しには「★4.9 16件のレビュー」と書かれ、評価の集計上は確かに16件分が反映されている。ところが、画面に並んで見えるレビューは、会員Aの「Good/Nice shirt!」と、会員Bの「形も素材も良い」の、たった2件だけ。残りの14件は、どこにも見当たりません。集計の件数と、実際に表示される件数が食い違う——この違和感が、調査の出発点になりました。

不審に思ったNさんは、ページVのHTML(Webページの構造を記述する言語。画面に表示される文字や画像の配置を定義する)を直接確認します。すると、奇妙な構造が見えてきました。HTMLの中には、確かに16件分のレビューに相当する記述が存在していたのです。表示が消えていたのではなく、HTMLの中には書かれている。にもかかわらず、ブラウザの画面には現れない。これは、ページの一部が「コメントアウト」されている可能性を示していました。コメントアウトとは、ソースコードの一部を「ここは処理しない/表示しない」と無効化する書き方のことです。

さらにNさんが目を凝らすと、会員Aによって15件ものレビューが投稿されていること、そしてそのレビュータイトル欄に、Webページとしては不自然な文字列——スクリプト(ブラウザ上で動く小さなプログラム)の断片が埋め込まれていることに気づきます。素材から読み取れる範囲では、会員Aのレビュータイトルには次のような断片が、別々の投稿に分かれて入っていました。

ある投稿のレビュータイトルには「Good」に続けて `<script>xhr=new XMLHttpRequest();/*` が。別の投稿のタイトルには `*/url1="https://□□□.co.jp/user/profile";/*` が。さらに別の投稿のタイトルには `*/xhr2.send(form);}</script>` が。これらはいずれも単体では意味をなさない断片です。しかし、ここに巧妙な仕掛けがありました。

レビュータイトルには50字という上限があります。長いスクリプトを一度に書き込むことはできません。そこで攻撃者は、スクリプトを複数の投稿に分割し、各投稿のあいだに挟まるHTML(他の会員のレビュー枠や、自分の他の投稿のタイトル・詳細欄など)を、JavaScriptのコメント記号 `/*` と `*/` で挟んで無効化したのです。`/*` から `*/` までの範囲はコメントとして読み飛ばされるため、本来そこにあるはずのレビュー14件分のHTMLがまとめて「コメントアウト」され、画面から消える。そして読み飛ばされた区間の前後に置いた断片だけがつながり、最終的に1本のまとまったスクリプトとして成立する——これが、利用者の目に映った「レビューが2件しか表示されない」という現象の正体でした。表示バグではなく、攻撃の副作用だったのです。

ここでNさんは、会員Aの投稿がクロスサイトスクリプティング(XSS。Webページに不正なスクリプトを埋め込み、それを閲覧した利用者のブラウザ上で実行させる攻撃)の脆弱性を悪用したものだと疑います。Webアプリ Qを調べたところ、会員が入力したスクリプトが、出力時にそのまま実行されてしまう脆弱性があることを確認しました。レビュータイトルに書かれたものが「ただの文字列」としてではなく「動くスクリプト」として画面に出力されてしまっていたのです。

Nさんは、ページVにアクセスしたときにブラウザで何が実行されるのかを突き止めるため、断片をつなぎ合わせてスクリプトを抽出しました。整形し、理解のためのコメントを補ったうえで眺めると、その意図がはっきりと浮かび上がります。

抽出されたスクリプトは、まず1つ目のXHR(XMLHttpRequest。JavaScriptがWebブラウザの裏側でサーバと非同期に通信するための仕組み)を使って「https://□□□.co.jp/user/profile」へGETでアクセスし、その応答をテキストではなくDOM(HTML文書をプログラムから扱える形にしたデータ構造)として受け取ります。そして応答の中から、id が「token」となっている要素の値、すなわちアップロードに必要なトークンを取り出します。ここがポイントです。この通信は同じドメイン(□□□.co.jp)に対するものなので、閲覧している会員のcookie(セッションID)が自動的に付与されます。つまり、被害会員本人になりすました形で、その人のトークンを正規に取得できてしまうのです。

トークンを手に入れたスクリプトは、続けて2つ目のXHRを準備し、「https://□□□.co.jp/user/upload」へPOSTでデータを送ります。ここで送るデータが恐ろしい。スクリプトは `document.cookie` でブラウザが保持しているcookie——すなわちセッションIDの文字列——を読み取り、それを「a.png」という名前、種類「image/png」のファイルオブジェクトに仕立て上げます。中身はセッションIDの文字列ですが、見かけ上は画像ファイルです。そして、このにせの画像ファイルを uploadfile として、先ほど取得したトークンを token として、アップロードリクエストに添えて送信するのです。

Webアプリ Qは、アップロードされた画像ファイルの形式をチェックしていませんでした。だから、中身がセッションID文字列であっても「a.png という画像」として受理してしまう。しかもこの通信も同じドメインへのものなので、被害会員のcookieが付き、トークンも一致するため、アップロードは正規の操作として成功します。結果として、被害会員のアイコン画像が、本人のセッションIDを書き込んだファイルに差し替えられてしまうのです。

仕上げはこうです。アップロードしたアイコン画像は、プロフィール設定ページやレビューページに表示されます。つまり、攻撃者は外から、被害会員のアイコン画像をダウンロードできる。そのファイルの中身を開けば、書き込まれているセッションIDの文字列を取り出せる。あとは、そのセッションIDを使って被害会員になりすまし、Webアプリ Qの各機能——購入履歴の閲覧やクレジットカード情報の登録ページなど、ログイン会員だけが使える機能——を本人のふりをして利用できてしまうのです。

ここで効いてしまったのが、もう一つの設定不備です。Webアプリ Qは、cookieにHttpOnly属性(JavaScriptからcookieを読み取れなくする設定)を付けていませんでした。HttpOnlyが付いていれば、`document.cookie` でセッションIDを読み取ること自体ができず、この窃取の流れは成り立ちません。出力時のエスケープ漏れ、画像形式の無検査、HttpOnly未設定——複数の小さな不備が重なって、一本の攻撃が完成していたのです。

ここから先は

8,317字 / 4画像
情報処理安全確保支援士に受かるにはとにかく事例をできるだけ多く知ることです。マガジン購入で他の年度記事へアクセス。現在量産中。全記事揃うまで半額提供のため購入はお早めに!

情報処理安全確保支援士(登録セキスペ)の午後問題、「読んでも頭に入らない」と感じていませんか?本シリーズでは、2017年〜2025年の過去…

この記事が気に入ったらチップで応援してみませんか?