Webセキュリティ基礎

XSS、CSRF、CORS、HTTPSなど、モダンWebアプリケーションを保護するための必須知識を学びます。

学習をスタートする

ロードマップ

1

XSSとCSRFの仕組みと対策

Webアプリケーションを構築する上で、避けて通れないのがセキュリティ対策です。本章では、代表的なWeb脆弱性である XSS(クロスサイトスクリプティング) と CSRF(クロスサイトリクエストフォージェリ) について、その仕組みと具体的な防衛策を学びます。 1. XSS(Cross-Site Scripting) XSSは、悪意のあるユーザーがWebサイトに不正なスクリプトを注入し、他のユーザーのブラウザ上でそれを実行させる脆弱性です。 種類 1. 反射型XSS (Reflected XSS): URLのパラメータなどに含まれる悪意あるスクリプトが、サーバーを介してそのままブラウザに返されて実行される形式。 2. 格納型XSS (Stored XSS): 掲示板やプロフィール画面など、データベースに保存された不正スクリプトが、他のユーザーがそのページを閲覧した際に実行される形式(最も危険)。 3. DOM-based XSS: サーバーを経由せず、JavaScriptが不適切にURLパラメータなどをパースして直接画面に出力(innerHTML 等を使用)することで発生する形式。 反射型XSSの攻撃フロー(図解) XSSの対策 1. エスケープ(サニタイズ)の徹底: ユーザーからの入力値を画面に出力する際は、HTMLタグとして解釈されないよう、特別な文字(, &, ", ')をHTMLエンティティに変換します。 2. ReactやNext.jsなどのフレームワークの使用: ReactはデフォルトでJSX内の変数を自動エスケープします。ただし、dangerouslySetInnerHTML を使用する場合は自動エスケープが無効化されるため、十分な注意が必要です。 2. CSRF(Cross-Site Request Forgery) CSRFは、ユーザーがログイン済みのWebサイトに対して、外部の悪意あるサイトから意図しないリクエスト(パスワード変更、購入処理など)を強制的に送信させる攻撃手法です。 攻撃フロー(図解) CSRFの対策 1. Cookieの SameSite 属性を設定する: SameSite属性を設定することで、サードパーティ(別のドメイン)からの遷移やリクエスト時にセッションCookieが送信されるのを防ぎます。 - Strict: 同一ドメインからのリクエストにのみCookieを送信(最も厳格)。 - Lax: 通常のリンク遷移(GETリクエスト)以外では外部ドメインからのCookie送信をブロック(モダンブラウザのデフォルト)。 - None: 制限なし(HTTPS必須)。 2. CSRFトークンの検証: リクエスト(POST, PUT等)を送信する際、クライアント側にワンタイムトークンを発行しておき、サーバー側で受信したトークンとセッション内に保持しているトークンが一致するかを検証します。外部サイトからのリクエストにはこのトークンを含めることができないため、攻撃を防ぐことができます。 まとめ * XSS は「自サイトの画面に他人のスクリプトを実行させる」攻撃。対策は HTMLエスケープ。 * CSRF は「他人のサイトから自サイトへリクエストを強制する」攻撃。対策は SameSite属性 と CSRFトークン。

2

CORSとCSPの仕組み

モダンWebブラウザには、セキュリティを維持しつつ異なるオリジン間で安全にリソースを共有・制限するための仕組みが備わっています。本章では、特に重要な CORS(オリジン間リソース共有) と CSP(コンテンツセキュリティポリシー) について解説します。 1. CORS(Cross-Origin Resource Sharing) ブラウザには本来、あるサイト(オリジン)から読み込まれたスクリプトが、異なるオリジンのリソースにアクセスすることを制限する 同一起源ポリシー(Same-Origin Policy) があります。 CORS は、サーバーが特定のオリジンに対してのみ「リソースへのアクセスを許可する」と明示することで、安全にこの制限を緩和する仕組みです。 * オリジン (Origin) とは: プロトコル + ホスト名(ドメイン) + ポート番号 の組み合わせです。 - https://example.com:443 と http://example.com:443 は異なるオリジン(プロトコルが違う) - https://api.example.com と https://example.com は異なるオリジン(ホストが違う) プリフライトリクエスト (Preflight Request) ブラウザは、データを書き換える可能性のあるリクエスト(POST や DELETE、あるいはカスタムヘッダーを持つ GET)を送信する前に、まず OPTIONS メソッド を使った「事前問い合わせ(プリフライト)」を送信し、通信が許可されているかを確認します。 CORSエラーが発生した場合の対策 CORSエラーは ブラウザがアクセスを遮断した ことで発生します。そのため、クライアント側ではなく、アクセス先の サーバー側 で正しいレスポンスヘッダーを返すように設定する必要があります。 2. CSP(Content Security Policy) CSP は、ブラウザが読み込みおよび実行を許可するリソース(JavaScript、CSS、画像、フレームなど)のホワイトリストを、Webサーバーがヘッダーを通じて指定するセキュリティ機能です。これにより、XSSなどで悪意あるスクリプトが注入された場合でも、その実行をブラウザ側で防ぐことができます。 CSPヘッダーの設定例 * default-src 'self': すべてのリソースのデフォルトは、現在のオリジン(自サイト)からのみ許可します。 * script-src 'self' https://trustedscripts.com: JavaScriptの読み込み元は、自サイトと trustedscripts.com のみに制限します。インラインスクリプト(HTML内に直接書かれた ...)は原則として実行が禁止されます。 * img-src 'self' data:: 画像は自サイトと Base64 エンコードされたデータスキームからのみ許可します。 Next.js でのメタタグ設定例 HTTPヘッダーを設定できない環境(静的ホスティング等)では、 タグを使って簡易的に設定することも可能です。 まとめ * CORS は、別オリジンからのアクセスを「許可」する仕組み。サーバーで Access-Control-Allow-Origin を設定する。 * CSP は、自サイトが読み込む外部リソースを「制限」する仕組み。XSSの防御に極めて有効。

3

HTTPSとセキュアCookieによる通信保護

インターネット上を流れるデータは常に盗聴や改ざんのリスクに晒されています。本章では、通信経路全体を保護する HTTPS(SSL/TLS) の仕組みと、ログイン情報を安全に保持するための セキュアCookie の設定について学びます。 1. HTTPS (Hypertext Transfer Protocol Secure) HTTPSは、HTTPに暗号化機能である SSL/TLS(Secure Sockets Layer / Transport Layer Security) を組み合わせたプロトコルです。以下の3つの重要な役割を持っています。 1. 暗号化 (Confidentiality): 通信内容を暗号化し、経路上の第三者による盗聴を防ぎます。 2. 改ざん検知 (Integrity): 通信データが途中で書き換えられていないことを保証します。 3. 認証 (Authentication): 接続先のサーバーが本物であることを「デジタル証明書」を用いて証明し、なりすましを防ぎます。 SSL/TLS ハンドシェイクの流れ(図解) 通信の開始時に、クライアント(ブラウザ)とサーバー間で暗号化に必要な情報(共通鍵)を安全に交換するプロセスです。 2. セッション管理とセキュアCookie 認証が完了した後、そのログイン状態を維持するために通常は「セッションID」や「JWTトークン」をCookieに保存してブラウザとサーバー間でやり取りします。このCookieが悪意あるスクリプトや盗聴によって盗まれないよう、適切な属性を設定する必要があります。 Cookieのセキュリティ属性 Cookieを発行する際は、以下の属性を必ず指定するようにします。 * HttpOnly: JavaScript (document.cookie など) からのアクセスを禁止します。これにより、万が一サイトがXSS脆弱性によって攻撃されても、Cookie内のセッションIDが不正にスクリプトで読み取られるのを防ぎます。 * Secure: HTTPS(暗号化通信)の時のみCookieを送信するように制限します。HTTP(非暗号化通信)での送信を防ぎ、ネットワーク上での盗聴リスクを排除します。 * SameSite: 前述の通り、外部の別サイトから送信されるリクエスト(CSRFなど)に対してCookieが添付される条件を制限します。 セキュアなCookie設定のコード例 (Next.js App Router) Next.js の Server Actions や Route Handlers において、Cookieを安全にセットする例を示します。 まとめ * HTTPS は公開鍵暗号と共通鍵暗号を組み合わせて、盗聴・改ざん・なりすまし を防ぐ。 * セッション管理で使用するCookieには HttpOnly、Secure、SameSite を必ず指定してセキュリティを強固にする。

4

OAuth 2.1、OpenID Connect とパスワードレス認証

Webアプリケーションが外部サービスと連携したり、より簡単で安全にログインできるようにするため、現代のWebセキュリティ設計では「外部認証・認可プロトコル」や「パスワードレス認証」が欠かせない要素となっています。 第4章では、混同しやすい「認証」と「認可」の違い、デファクトスタンダードである OAuth 2.1 / OIDC、そしてパスワードの漏洩リスクを根本的に排除する Passkey(パスワードレス認証) について学びます。 1. 「認証」と「認可」の違い セキュリティの設計において、認証(Authentication)と認可(Authorization)は明確に区別する必要があります。 * 認証 (Authentication): 「あなたは誰ですか?」を確認する処理(本人確認)。 例: ID/パスワードの入力、生体認証、ワンタイムコードによる本人確認。* * 認可 (Authorization): 「あなたには何をする権限がありますか?」を確認・付与する処理(権限移譲)。 例: サードパーティ製アプリに対し、「私の代わりにGoogleカレンダーの予定を読み込む権利」を与えること。* 2. OAuth 2.1 と OpenID Connect (OIDC) OAuth 2.1 とは? OAuth(オーオース)は、安全に「認可」を行うための業界標準プロトコルです。最新の OAuth 2.1 では、セキュリティの脆弱性が指摘されていた古い仕組み(Implicit Flowなど)が廃止され、セキュリティ強度の高い 認可コードフロー + PKCE が事実上の必須仕様となりました。 [!NOTE] PKCE (ピクシー) とは、クライアントが一時的な暗号コード(コード検証子)を生成し、認可コードの引き換え時にそれを照合することで、途中でコードが盗まれても不正利用を防ぐ仕組みです。 OpenID Connect (OIDC) とは? OAuth 2.0 はあくまで「認可(権限付与)」のための仕様であり、誰がログインしたかを証明する「認証」の仕組みを持っていませんでした。そこで、OAuth の上に本人情報を伝えるレイヤーを重ねたのが OpenID Connect (OIDC) です。これにより、ソーシャルログイン(GoogleやGitHubアカウントでのログイン)が実現しています。 OIDC ログイン認証フロー(図解) 3. パスワードレス認証(FIDO2 と Passkey) 長年使われてきた「ID/パスワード」の認証方式には、パスワードの使い回しによるアカウント乗っ取りや、偽サイトに入力させて資格情報を盗む「フィッシング詐欺」といった重大な欠陥があります。 これを根本から解決するために策定された標準規格が FIDO2(ファイドツー) であり、それを一般のユーザーが手軽に扱えるようにした実装が Passkey(パスキー) です。 パスキーの動作原理 パスキーは、公開鍵暗号方式を採用しています。 1. 登録時: デバイス(スマートフォンやPCの生体認証)で「秘密鍵」を生成して安全に保管し、サーバーへ「公開鍵」を登録します。 2. 認証時: サーバーから送られてきたランダムなデータ(チャレンジ)に対し、デバイスの秘密鍵で署名を作成してサーバーへ送り返します。サーバーは登録済みの公開鍵を使って署名を検証し、本人であることを確認します。 主なセキュリティ上の強み * パスワードの廃止: サーバー側には「公開鍵」しか保存されないため、万が一サーバーからデータが漏洩しても、アカウントが乗っ取られる心配はありません。 * 強力なフィッシング耐性: 署名処理はアクセスしているドメイン名(URL)と紐づいて実行されるため、ユーザーが巧妙なフィッシングサイトに騙されて騙しリンクを踏んだとしても、ブラウザがドメインの不一致を検知して署名を拒否します。 * デバイス同期: クラウドキーチェーン(iCloudやGoogleパスワードマネージャーなど)を介してデバイス間で秘密鍵が安全に同期されるため、端末の紛失時にもアカウントへのアクセスを復旧できます。 まとめ * 認証は「本人の特定」、認可は「権限の付与」を指す。 * OAuth 2.1 はセキュリティを強化した認可プロトコルで、すべてのクライアントで PKCE の利用が必須化される流れにある。 * OpenID Connect (OIDC) は OAuth の仕組みを拡張した「認証」プロトコルで、ソーシャルログインを支えている。 * Passkey は公開鍵暗号方式を利用したフィッシング耐性の高いパスワードレス認証方式。

5

セッション管理とJWTセキュリティ

Webアプリケーションでユーザーのログイン状態を保持する「セッション管理」は、ハッカーから最も狙われやすい部分の一つです。近年では従来のセッションID方式に加え、SPA(Single Page Application)の普及に伴い JWT(JSON Web Token) を使ったステートレスなトークン認証が主流となっていますが、これには特有のセキュリティ上の注意点があります。 第5章では、セッションIDとJWTの仕組みの違いと、トークンを安全に管理するためのベストプラクティスについて学びます。 1. セッション管理の2つのアプローチ ユーザーがログインしたことを示す「認証状態」を管理するアプローチは、主に以下の2種類です。 * セッションID方式 (Stateful): * サーバー側に「セッション情報(誰がいつログインしたか)」を保存し、クライアントにはその場所を示すランダムな文字列(セッションID)のみを渡します。サーバーのメモリやDBを消費しますが、サーバー側から強制的にログアウト(セッション破棄)させることが容易です。 * JWT方式 (Stateless): * ユーザーIDや権限などの情報をJSONデータにし、サーバーの秘密鍵でデジタル署名したトークンをクライアントに丸ごと持たせます。サーバーはDBに問い合わせることなく、署名を検証するだけで認証が完了するためスケーラブルですが、一度発行したトークンを有効期限前に無効化(強制ログアウト)するのが難しいというトレードオフがあります。 2. JWT(JSON Web Token)の構造 JWTはドット(.)で区切られた3つの文字列(Base64URLエンコード)で構成されています。 1. Header (ヘッダー): トークンのタイプ(JWT)や、使用されている署名アルゴリズム(例: HS256, RS256)を示します。 2. Payload (ペイロード): 実際のデータ(クレーム)が含まれます(例: ユーザーID、名前、有効期限 exp)。※Base64でエンコードされているだけなので、誰でもデコードして中身を読むことができます。パスワードなどの機密情報は絶対に含めてはいけません。 3. Signature (署名): ヘッダー、ペイロード、サーバーしか知らない秘密鍵を合わせてハッシュ化して生成します。これにより、クライアント側でペイロードが改ざんされた場合、サーバー側で署名の検証が失敗し、改ざんを検知できます。 3. トークンの安全な保存先とCookie属性 クライアントでトークンをどこに保存するかは、XSS(JavaScriptインジェクション)とCSRF(リクエスト強要)の脆弱性に対するトレードオフになります。 Web Storage (localStorage) への保存 * メリット: 実装が非常に簡単。CSRF攻撃に対して完全に安全(リクエストに自動付与されないため)。 * デメリット: XSS脆弱性がある場合、悪意あるJavaScriptによってトークンが簡単に読み取られ、盗まれてしまいます。 Cookie への保存(推奨) セキュリティのために以下の Cookie属性 を厳格に付与して保存します。 * HttpOnly: JavaScriptからのCookieの読み取りを禁止します。これで XSSによるトークン窃取を防ぎます。 * Secure: HTTPS通信(暗号化)のときのみCookieを送信します。 * SameSite=Lax / Strict: 他のサイト(クロスサイト)からのリクエスト時にCookieが送信されるのを制限します。これで CSRF攻撃を防ぎます。 セッションストアの負荷軽減のために安易にLocalStorageにJWTを保存する設計は避け、セキュアな属性を設定したCookieを使用することが、実務における安全な認証設計の鉄則です。