33 ポイント 投稿者 GN⁺ 2025-03-04 | 1件のコメント | WhatsAppで共有
  • Cross-Site Request について考えているうちに、CSRF保護とCORSの両方が必要だということが最初は理解できなかった。しかし、これを説明するには多くの言葉が必要になる

CSRFとCORS

  • CSRF (Cross-Site Request Forgery)
    • 以前はよく見られたが、現在はほとんどのWebフレームワークがデフォルトで保護機能を提供しているため、ほぼ問題にならない
    • 攻撃方法: ユーザーに悪意のあるサイトで特定のフォームをクリックさせ、クロスサイトリクエストを送信するよう誘導する
    • 防御方法: リクエストが他サイトから流入したものではないかを確認する
  • CORS (Cross-Origin Resource Sharing)
    • HTTP仕様の一部であり、特定のクロスサイトリクエストを許可する方法を定義する
    • プリフライトリクエストとレスポンスヘッダーを使って、どのオリジンからリクエストを送れるかを指定する

では、クロスサイトリクエストはデフォルトで許可されていてCSRF保護が必要なのか、それともデフォルトでブロックされていてCORSによって許可する必要があるのか? 答えは 両方 である。

基本的な動作

  • 同一オリジンポリシー (Same-origin policy)
    • ブラウザが強制するセキュリティポリシー
    • 一般に クロスサイト書き込み (Write) は許可 され、読み取り (Read) は禁止 される
    • たとえば、ブラウザはフォームによるPOSTリクエストは許可するが、レスポンスを読むことはできない
  • SameSite Cookieポリシー
    • 2019年にCookieのデフォルト動作が変更された
    • 以前はクロスサイトリクエストでもCookieは常に送信されていた
    • 新しい SameSite 属性が追加され、デフォルト値が Lax に変更された
    • 2025年時点で、96%のブラウザが SameSite 属性をサポート し、75%が新しいデフォルト値 (Lax) をサポート している
    • しかしSafariはこれをデフォルト値として適用しておらず、UCBrowserも依然としてサポートしていない
  • サイト (Site) とオリジン (Origin) の違い
    • オリジン (Origin): プロトコル + ホスト名 + ポート の組み合わせ
    • サイト (Site): プロトコル + トップレベルドメイン + 1 の組み合わせ(サブドメインとポートは無視される)

CORS

  • CORSは同一オリジンポリシーに対して、特定のオリジンに限って例外的に許可する仕組み である
  • ブラウザはリクエストを送る前に OPTIONS タイプの プリフライトリクエスト を送信する
  • サーバーはレスポンスヘッダーを通じて許可ルールを定義する(Access-Control-* ヘッダーを使用)
  • CORSが適用されるリクエストの種類:
    • fetch および XMLHttpRequest
    • Webフォント
    • WebGLテクスチャ
    • canvasdrawImage により描画された画像/動画フレーム
    • CSS shape-outside プロパティで使用される画像
  • ただし、フォーム送信には例外的にCORSが適用されない
    • HTML 4.0 の <form> タグは昔からクロスサイトリクエストを許可していた
    • したがって既存のサーバーは、すでにCSRF攻撃を防御するよう設計されている必要があった
    • サーバーがレスポンスを共有するには Access-Control-Allow-Origin を設定する必要があるが、リクエスト自体はプリフライトなしでも受け入れられる

質問: SameSite ポリシーとこの方式は、どのように一貫性を保っているのか?

CSRF保護の方法

  • クロスサイト書き込みリクエストは許可されるが、レスポンスは共有されない
    • ほとんどのWebサイトでは、クロスサイト書き込みを許可したくない
  • 標準的なCSRF防御方法
    • ユーザーごとの CSRFトークン をリクエストに含めて検証する
    • 方法:
      • フォーム送信: hidden inputとしてトークンを追加
      • JSリクエスト: Cookieまたは meta タグに保存し、リクエストヘッダーやパラメータに含める
  • JSリクエストはもともとクロスサイトではデフォルトでブロックされる
    • ただし同一サイトリクエスト (same-site request) では許可される
    • CSRFトークンを含めれば、すべてのリクエストを同じ方法で検証できる
  • 追加のセキュリティ上の利点
    • ブラウザがデフォルトでレスポンスの読み取りをブロックするという前提の上で機能する
    • Origin ヘッダーを検査するよりも安全性が高い

質問: 一部のフレームワークではCSRFトークンを定期的に変更するが、その理由は何か?

ブラウザの役割

  • Webセキュリティの核心は ブラウザを信頼できるかどうか にかかっている
  • ブラウザは次のことを行う:
    • 同一オリジンポリシーを強制する
    • レスポンスが許可されていない場合は読み取れないようにブロックする
    • SameSite=Lax のデフォルト値を適用するかどうかを決定する
    • CORSを実装し、安全なプリフライトリクエストを送る

私たちは利用しているブラウザを信頼しなければならない。

結論

  • SameSite=Lax が100%のブラウザでサポートされれば、セキュリティはさらに強化されるだろうが、
    現状では依然としてクロスサイト POSTリクエストだけが例外的に許可 されている
  • そのため開発者は、引き続きCSRF保護を考慮する必要がある

「インターネットはますます安全になっているが、その分だけ過去との互換性も次第に失われている。」

出典

  1. Same-origin policy
  2. caniuse SameSite cookie attribute
  3. OWASP CSRF cheatsheet
  4. CORS wiki with requirements
  5. CORS spec
  6. CORS on MDN
  7. Preflight request
  8. Origin request header
  9. Origin and Site

1件のコメント

 
GN⁺ 2025-03-04
Hacker Newsのコメント
  • CORSは、サーバーがブラウザに対して、どのクロスオリジンリクエストがレスポンスを読み取れるかを明示的に伝える仕組みである

    • 基本的にブラウザは、クロスオリジンのスクリプトがレスポンスを読むことをブロックする
    • 明示的に許可されていない限り、リクエスト元のドメインはレスポンスを読むことができない
    • たとえば、evil.com のスクリプトが bank.com/transactions にリクエストを送り、被害者の取引履歴を読み取ろうとすることがある
    • ブラウザはリクエストが bank.com に到達することは許可するが、evil.com がレスポンスを読むことはブロックする
  • CSRF保護は、認証済みユーザーになりすました悪意あるクロスオリジンリクエストが、無断で操作を実行することを防ぐ

    • たとえば、evil.com のスクリプトが bank.com に対して操作を実行するリクエストを送ることがある(例: bank.com/transfer?from=victim&to=hacker で送金する)
    • bank.com のサーバー側のCSRF保護はこれを拒否する(おそらくリクエストに秘密のCSRFトークンが含まれていないため)
  • CSRF保護は書き込み保護に関するものであり、CORSは読み取り保護に関するものである

  • JSから開始されたリクエストは、デフォルトではクロスサイトでは許可されない

    • fetch() を使い、許可されたヘッダーだけを使えばクロスサイトリクエストを開始できる
  • このトピックについては、より良い説明があると思う

    • 関連ブログへのリンクあり
  • ブログ記事の質問への応答

    • HTML 4.0 の <form> 要素は、どのオリジンに対してもシンプルリクエストを送信できる
    • これが SameSite イニシアチブとどう整合するのかという質問があった
  • 2022年にMDNのCORS記事へ、「シンプルリクエスト」という用語の由来を明確にするための段落を追加した

    • 以前は、fetch 仕様では言及されていないとだけ書かれていた
    • 2019年のブラウザのCSRF防止機能が SameSite=Lax をサポートしている、またはデフォルトに設定している場合への言及がなかった
  • SameSite が CORS のプリフライトとは独立して追加されたことが混乱を招く

    • ブラウザベンダーが、すべてのクロスオリジン POST リクエストにプリフライトを必須にしなかった理由が気になる
  • csrf を使わなくても安全だと考えるかもしれないが、一部のライブラリ(例: django rest framework)は Content-Type ヘッダーが設定されていれば HTML フォームを処理できる

    • そのため、ユーザーのサイトにフォームを投稿して、ユーザーになりすましたリクエストを送れる
  • CSRFトークンがローテーションされる理由についての質問

    • OWASP はそのほうが安全だとしているが、理由がよくわからない
  • 複雑なトピックについてのフローチャートを求める声

    • 新しいアプリケーションプラットフォームと標準セットを望んでいる
  • こうした仕組みは、簡単な診断トレースを支援してくれない

    • 正しく設定されていない正当なユースケースに対する不透明なエラーを何度も経験した
  • CORSが登場する前から、ページのオリジンではない任意のエンドポイントにリクエストを送れていたのに、なぜレスポンスは見られなかったのか理解できない

    • それが仕様に偶然含まれたのか、XSSを想定して意図的にそうしたのか、あるいは先行ブラウザがそう実装し、他のブラウザが追随したのか気になる
  • CSRF保護に関する混乱

    • 攻撃者が goodsite.com からCSRFトークンを取得して badsite.com に埋め込み、Alice をだまして badsite.com から goodsite.com へリクエストを送信させるのを、どう防ぐのかが疑問