/note/tech

令和時代の API 実装のベースプラクティスと CSRF 対策

要約:

■ 1. CSRF攻撃の成立条件

  • CSRF(Cross-Site Request Forgery)は古くから知られる攻撃手法である
  • 攻撃者が用意したサイトに仕込まれたフォームから、ユーザがログイン中のサービスに対してリクエストを送信する攻撃である
  • ログイン済みのCookieが付与されたリクエストがサービスの投稿APIに準拠していれば、そのユーザのアカウントで不正な投稿が受理される
  • 攻撃の手軽さと影響の大きさによって、XSSやSQLインジェクションと並ぶ有名な攻撃手法として認知された
  • 従来の対策としては、One Time Token(CSRFトークン)をフォームに仕込み、トークンの一致によって投稿を受理する方法が一般的だった

■ 2. CSRF成立の本質的問題点

  • CSRF攻撃が成立する根本原因は「攻撃者のフォームからのリクエストにもサービスのCookieが付与される」点である
  • しかし、CSRFトークンが機能していた事実は「このリクエストはどこから来たものなのか」が分かれば対策できる証拠である
  • 本来注目すべき欠落は「リクエストの出自がわからない」という点である
  • SameSite Cookieの導入によって別のサイトからのリクエストにCookieが付与されないようにすることは対策として成立するが、本質的な問題はリクエストの出自が不明であることである

■ 3. Originヘッダの付与

  • プラットフォームの回答は「リクエストにOriginヘッダを付与する」というものである
  • 現在のブラウザでフォームをsubmitすると、送られるリクエストにOriginヘッダが付与される
  • Originヘッダの値を確認すれば、リクエストが正規のサイトからではないことを容易に判別できる
  • Cookieの有無に関わらず、サービスは「意図しないOriginからのリクエスト」を弾くことができる
  • このヘッダの必要性は少なくとも16年前から議論されており、7年前にFirefoxが実装することで全てのブラウザがフォームのsubmitにOriginヘッダを付与するようになった
  • SameSite Cookieが導入されるずっと以前から「リクエストの出自を知る」ことは可能であり、それを用いて攻撃リクエストを弾くことができた
  • fetch()を用いた実装でOriginを確認しながら適切なAccess-Control-Allow-Originを返していれば、「どこから来たかわからないリクエスト」を弾くことはずっと可能であった

■ 4. SameSite Cookieの副次的効果

  • SameSite Cookieの登場により、積極的な対策をしてこなかったサービスも受動的な変更によって保護される結果になった
  • しかしこれはかなり副次的な効果である
  • SameSite Cookieの本来の目的は3rd Party Cookieをマークすることであり、3rd Party Cookie Deprecateの終着点はSameSite=None Cookieが送られないようにすることである
  • CSRFは3rd Party Cookieよりも遥かに古くから問題だったが、「サイトを跨いだCookieが送られること」よりも「リクエストの出自がわからないこと」の方がプラットフォームが対策すべき問題とされていた
  • SameSite Laxがデフォルトになったことを理由に「Originのチェック」を怠った実装は、本質的な対策を怠った片手落ちの実装である

■ 5. CSRFトークンの必要性の再検討

  • OWASPのCSRF Cheat Sheetでは今でもトークンベースの対策が推奨されている
  • トークンベースの対策を推奨する理由として以下が挙げられる:
    • XSSがあった場合の対策
    • ヘッダを改変している可能性
    • GETでAPIがあると成立する攻撃
    • サブドメインが乗っ取られる攻撃
  • XSS問題の反論:
    • XSS自体を対策すべきであり、XSSによってDOMに展開されたCSRFトークンを盗めない道理はない
  • ヘッダ改変問題の反論:
    • ブラウザや拡張に脆弱性があれば、サービス側の対策はバイパスできるため、サービス提供者が想定すべき対策として視点がずれている
    • ユーザ設定やProxyが意図的にOriginヘッダを改変する環境は、ルールを守っていないためサービス側が許容する必要はない
  • GET API問題の反論:
    • 様々な条件でSameSiteやOriginが機能しないリクエストを生成できるという指摘は、「APIがGETだった場合に攻撃が成立する」という条件に収束する
    • 副作用のあるAPIをGETで提供している実装は前提として間違っている
  • サブドメイン攻撃の反論:
    • SameSite Cookieだけに依存した対策は問題があるため、一次防御は「リクエストのOriginのチェック」である必要がある
  • CSRFトークンの利点:
    • 実装がこなれており堅牢である
    • フレームワークがデフォルトで提供することが多く、導入コストが低い
    • 現状入っているなら積極的に外す理由はない
  • 防御の認識の変更:
    • CSRFトークンは一層目の防御ではなく、多層防御の二層目であると認識すべきである
    • トークンを使用していても「リクエストの出自を確認する」実装は一層目にあるべきである
    • 全ての場所でOriginを確認するのがプラクティスである

■ 6. Fetch Metadataの導入

  • 本来は全てのリクエストにOriginをつけるべきだが、「OriginヘッダのあるリクエストはXHRからのもの」という前提の実装が蔓延していたため、ドラスティックな変更ができなかった
  • Originヘッダとは別にFetch Metadataが定義された
  • 現在のブラウザでは以下のヘッダが付与される:
    • Sec-Fetch-Dest
    • Sec-Fetch-Mode
    • Sec-Fetch-Site
    • Sec-Fetch-User
  • リクエストの出自を確認する手法は整備されており、これらを無視することはプラットフォームが差し伸べている手を振り払うことと同じである

■ 7. 令和時代の実装プラクティス

  • 副作用があるエンドポイントの実装における優先順位:
    • POSTにする(副作用のあるAPIをGETにしない)
    • Originを確認する
    • SameSite Lax/Strictを明示する
    • Fetch Metadataも確認する
  • Fetch Metadataのサポートに不安がある場合は「存在したら値をチェックする」実装でも良い
  • Sec-プレフィックスはJavaScriptから操作できないヘッダであるため、値がある場合だけのチェックでも意味がある
  • 実装の特徴:
    • 追加のストレージコストが不要である
    • コードだけで実装できるためレイテンシーが最小である
    • フレームワークのレールの基盤として存在することが望ましい
  • この実装から逸脱するコードが必要になった場合、プラットフォームが推奨するレールから外れていることを認識した上で、CORSなどの適切な対策をしながら拡張すべきである
  • このベースを伴わずにトークンを載せても片手落ちである
  • この実装をベースにしてもトークンがないと防げないCSRFが可能であれば、それはプラットフォームにおけるバグの可能性が高く、W3CのWebAppSecなどで議論すべき題材である