Slide 1

Slide 1 text

Rails APIモードのための シンプルで効果的なCSRF対策 2024/10/25-26 Kaigi on Rails 2024 株式会社Leaner Technologies / @corocn

Slide 2

Slide 2 text

2 @corocn / ころちゃん

Slide 3

Slide 3 text

まずは、おさらいから 3

Slide 4

Slide 4 text

CSRF - Cross-Site Request Forgeries - クロスサイト・リクエスト・フォージェリ - しーさーふ - サイバー攻撃の一種 - 代表的な手口 - 既存のセッションを悪用して意図しない動作をさせる(Session Riding) - 今日はこのケースを前提として話す 4

Slide 5

Slide 5 text

5 ブラウザ サーバー 正規サイト 罠サイト リクエスト時に CookieでセッションIDが自動送信される Cookie: SESSION_ID=abc セッションチェック ヨシ! ※ログイン後(Cookieでのセッション確立後)だと思って見てください

Slide 6

Slide 6 text

6 ブラウザ サーバー 正規サイト 罠サイト Cookie: SESSION_ID=abc Cookie: SESSION_ID=abc ヨシ! Cookieが自動送信されることに起因する攻撃 => APIモードでもCookie使うと対策が必要 ※Cookie以外でも発生するが今日は一旦忘れて説明する(Basic認証とか...) ヨシ! ※ログイン後(Cookieでのセッション確立後)だと思って見てください 悪意のあるサイトからも 勝手に送信されてしまう

Slide 7

Slide 7 text

Railsはトークン方式で対策 - CSRFトークンを発行してチェックする - OWASP だと Synchronizer Token Pattern と呼ばれている - https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Preve ntion_Cheat_Sheet.html - フルスタックFWでよく見られる対策 - Laravel(PHP)も同様 7

Slide 8

Slide 8 text

8 ブラウザ サーバー 正規サイト 罠サイト (1) トークンを発行 & セッションに紐づけて保存

Slide 9

Slide 9 text

9 ブラウザ サーバー 正規サイト 罠サイト (2) トークン表示 (1) トークンを発行 & セッションに紐づけて保存

Slide 10

Slide 10 text

10 ブラウザ サーバー 正規サイト 罠サイト Cookie: SESSION_ID=abc (3) トークン送る (2) トークン表示 (1) トークンを発行 & セッションに紐づけて保存

Slide 11

Slide 11 text

11 ブラウザ サーバー 正規サイト 罠サイト Cookie: SESSION_ID=abc (3) トークン送る トークン一致 = 正規サイトだと判定して、 リクエストを許可する (2) トークン表示 (4) トークン検証 (1) トークンを発行 & セッションに紐づけて保存

Slide 12

Slide 12 text

12 ブラウザ サーバー 正規サイト 罠サイト Cookie: SESSION_ID=abc Cookie: SESSION_ID=abc (3) トークン送る (2) トークン表示 (4) トークン検証 罠サイトからのリクエストは トークンが一致しないのでエラー (1) トークンを発行 & セッションに紐づけて保存

Slide 13

Slide 13 text

Railsはトークン方式で対策(実装) - 詳しくはRails セキュリティガイド(上記コードもこちらから引用) - https://railsguides.jp/security.html 13 View Controller

Slide 14

Slide 14 text

ここから本題 14

Slide 15

Slide 15 text

課題 - Rails API + SPA のプロダクト開発によく出会うようになった - Rails サーバーとは別に React 等でフロントエンドを別でホストするケース - 今の仕事もこれ - ※SPA = Single Page Application - CSRFトークンを使った対策をどう組み込むのがベストか分からない 15

Slide 16

Slide 16 text

- ViewとControllerで数行書けば対策が終わる - これこそフルスタックの良さ - フロント・バックエンド双方が協調して成立するような防御手法を提供しやすい - Ruby on Rails Guides のようなドキュメントを提供しやすい(初学者にも優しい) Rails wayに乗っかる場合 16 View Rails Controller トークンの送信 csrf_meta_tags ヘルパの提供 トークンの生成 protect_from_forgery での検証 csrf_token

Slide 17

Slide 17 text

- Rails wayから外れるのでトークン送受を自前で書くことになる - どう返して、どう管理して、どう渡す? - 公開されている事例も少ない Rails API + SPA の場合 17 View (SPA) Rails Controller protect_from_forgery での検証 ブラウザ Cookie: SESSION_ID= どうすれば?

Slide 18

Slide 18 text

対策方法を考えよう - トークン方式にこだわりはない - 自分は標準機能にひっぱられる悪い癖があるので、フラットに考えよう - もっとシンプルに対策できる方法ない? 18

Slide 19

Slide 19 text

余談:「Rails SPA CSRF 対策」で調べると... - 検索すると私が3年前に書いた記事がヒットする - トークン方式を無理やり使う方法を解説している - もっとシンプルな方法あるよ!と伝えたいのも今日の発表のモチベ 19

Slide 20

Slide 20 text

対策 20

Slide 21

Slide 21 text

結局どうすれば対策できるの? - 方向性1: リクエストの出どころを確認する - 許可したサイトからのリクエストのみ受け入れる - 方向性2: Cookieが自動で送られないようにする - 悪意のあるサイトからCookieが自動で送られなければログイン状態にならない 21

Slide 22

Slide 22 text

方向性1: リクエストの出どころで判別する 22 ブラウザ サーバー 正規サイト 罠サイト このリクエスト元はOK! このリクエスト元はNG

Slide 23

Slide 23 text

方向性2: Cookieが自動で送られないようにする 23 ブラウザ API サーバー 正規サイト 罠サイト 自動送信される Cookie: SESSION_ID=abc Cookie: SESSION_ID=abc ↑自動で送られなければログイン扱いにならない

Slide 24

Slide 24 text

具体的なCSRF対策 3つ - 基本 - リクエストの出どころを確認する - Origin ヘッダーの確認 - より安全に - リクエストの出どころを確認する - Fetch Metadata の確認 - Cookieが自動で送られないようにする - SameSite 属性の指定 24

Slide 25

Slide 25 text

具体的なCSRF対策 3つ - 基本 - リクエストの出どころを確認する - Origin ヘッダーの確認 - より安全に - リクエストの出どころを確認する - Fetch Metadata の確認 - Cookieが自動で送られないようにする - SameSite 属性の指定 25

Slide 26

Slide 26 text

Originヘッダー - ブラウザがリクエスト発生元をヘッダに付与して送信してくる - Origin は プロトコル x ホスト x ポート番号 で定義される - https://kaigionrails.org/2024/about であれば - https://kaigionrails.org 部分が Origin - 参考 - https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Origin - https://developer.mozilla.org/ja/docs/Web/Security/Same-origin_policy 26

Slide 27

Slide 27 text

27 ブラウザ サーバー 正規サイト 罠サイト Origin: https://abunai-page.com 知らない!NG! リクエストの出どころを確認 = 想定されるOriginのみ許可 Origin: https://app.example.com 想定してる!OK! https://abunai-page.com https://app.example.com シンプル!

Slide 28

Slide 28 text

ここで疑問 - Q. 既存の実装はなぜトークン方式なの? Originのがシンプルでは? 28

Slide 29

Slide 29 text

ここで疑問 - Q. 既存の実装はなぜトークン方式なの? Originのがシンプルでは? - A. ブラウザが Origin を送ってくれないケースがあったから 29

Slide 30

Slide 30 text

昔は Form POST で送られなかった 30 送信方法 メソッド オリジン関係 ブラウザ 送信するようになった バージョン 備考 Form POST 同一オリジン Chrome系 v73 ?? 2019/03 リリース チケットの詳細確認できず Firefox v70 2019/10 リリース https://bugzilla.mozilla.org/show_bug.cgi?i d=1424076 Safari v11 ?? 2019/09 リリース チケットの詳細確認できず Edge (Legacy) v15 ?? 2017/04 リリース Edge Legacy自体が、 2021/03/09 サポート終了 現在ではすべてのブラウザの Form POST で Origin が送信される。 断片的な情報しかなく参考程度だが、5年前にはすべてのブラウザで対応が終わっている

Slide 31

Slide 31 text

現在も送られないケース - GET | HEAD かつ 同一オリジン のとき - GETで副作用を発生させないよう作れば、このケースは無視できる - 副作用が発生するメソッドでは Origin は送られてくると思って問題ない 31

Slide 32

Slide 32 text

補足資料: 例外も一応ある - https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Origin を見てね - 仮に送られなくても、リクエスト失敗(安全側に処理される)になるだけ 32

Slide 33

Slide 33 text

補足資料: 同一オリジン・クロスオリジン 33 オリジンA オリジンB 同一?クロス? https://kaigionrails.com/ https://kaigionrails.com/2024 同一オリジン(パス違いはOK) http://kaigionrails.com クロスオリジン: プロトコルが異なる https://x.kaigionrails.com クロスオリジン: ホストが異なる https://kaigionrails.com:80 クロスオリジン: ポートが異なる

Slide 34

Slide 34 text

補足資料: 同一オリジン・クロスオリジン - クロスオリジンリクエスト - https://app.example.com/ (frontend) => https://api.example.com/ (API) - 同一オリジンリクエスト - https://app.example.com/ (frontend) => https://app.example.com/api/ (API) - Reverse Proxy を前段に配置するケースはこれ - https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Origin 34

Slide 35

Slide 35 text

具体的なCSRF対策 3つ - 基本 - リクエストの出どころを確認する - Origin ヘッダーの確認 - より安全に - リクエストの出どころを確認する - Fetch Metadata の確認(ココ) - Cookieが自動で送られないようにする - SameSite 属性の指定 35

Slide 36

Slide 36 text

Fetch Metadata - 不正なリクエストを弾くための情報を提供するヘッダ - 4種類あるが Sec-Fetch-Site を使うのが良い - Sec-Fetch-Site - Sec-Fetch-Dest - Sec-Fetch-Mode - Sec-Fetch-User 36

Slide 37

Slide 37 text

Sec-Fetch-Site - リクエスト発生元 - リクエスト先の関係性 - same-site, same-origin, cross-site, none が取りうる値 - 基本は same-site, same-origin を見れば防御できる - ざっくり - same-origin: オリジン完全一致 - same-site: サイト内(サブドメインはcross-originだがsame-site) - https://web.dev/articles/same-site-same-origin 37

Slide 38

Slide 38 text

Fetch Metadata の良いところ - Originの使いにくい点を解消してくれる - Origin はそもそも付与されたり付与されなかったりするので複雑 - リクエストの文脈情報が提供されない -> 詳細に提供してくれる - より強固なセキュリティポリシーに対応できる 38

Slide 39

Slide 39 text

Sec-Fetch-Site のサポート状況 39 ブラウザ サポート開始 リリース日 備考 Chrome v76 2019/07/30 Firefox v90 2021/07/13 Safari v16.4 2023/03/28 macOS Venture のサポート終了で非対応バージョンがなくなる。 正確に公表されてないが、 2027年あたり? Edge(Chromium) v79 2020/01/15 わりと新しい仕様なので、ヘッダがあればチェックする方針になりそう

Slide 40

Slide 40 text

具体的なCSRF対策 3つ - 基本 - リクエストの出どころを確認する - Origin ヘッダーの確認 - より安全に - リクエストの出どころを確認する - Fetch Metadata の確認 - Cookieが自動で送られないようにする - SameSite 属性の指定 40

Slide 41

Slide 41 text

41 ブラウザ API サーバー 正規サイト 罠サイト ブラウザが自動送信する Cookie: SESSION_ID=abc Cookie: SESSION_ID=abc ↑自動で送られないように設定したい Cookieが送信されないようにすれば良い

Slide 42

Slide 42 text

SameSite属性 - Cookieの属性 - クロスサイトでのCookie送信の挙動を制御するもの - 3rd Party Cookieの制御のための目的で作られた - CSRF対策で使えるのはオマケ - 広告・プライバシー文脈のほうが強い 42

Slide 43

Slide 43 text

SameSite属性の種類 - None - すべてのリクエストでCookie送信 - Lax - 同一サイトからのリクエスト - 一部のクロスサイトリクエスト(トップレベル + GETメソッド)に対して送信される - Strict - 同一サイトからのリクエストのみ => Lax or Strictを指定すれば良い 43

Slide 44

Slide 44 text

SameSite属性 の2分ルール - SameSite=Laxの場合 - セットされた2分間はPOSTリクエストでもCookieが送信される - 古いシステムや特定のシナリオで必要になったり、 - ポータルからPOSTで別サービスでログインするようなケース。 - CSRF対策としては片手落ちになるので 他のチェックも併用 44

Slide 45

Slide 45 text

SameSiteのサポート状況 45 ブラウザ サポート開始 リリース日 デフォルト値の設定 リリース日 デフォルト値 Chrome v51 2016/05 v80 2020/02 Lax Firefox v60 2018/05 2022年01に一度導入された がキャンセル None Safari v12 2018/09 - None Edge(Chromium) v79 2020/01 v80 2020/02 Lax 現行ブラウザはすべて対応しているが、デフォルトNoneのブラウザがある 明示的なLaxの指定が必要 参考 https://caniuse.com/?search=samesite

Slide 46

Slide 46 text

Railsでの実装 46

Slide 47

Slide 47 text

47

Slide 48

Slide 48 text

48

Slide 49

Slide 49 text

SameSite 指定 - config.action_dispatch.cookies_same_site_protection - 6.1以降はデフォルト Lax なので指定不要 - https://railsguides.jp/configuring.html 49

Slide 50

Slide 50 text

まとめ 50

Slide 51

Slide 51 text

具体的な対策のまとめ - 基本 - リクエストの出どころを確認する - Origin ヘッダーの確認 => 基本的に送られてくるのでこれをメインでチェック - より安全に - リクエストの出どころを確認する - Fetch Metadata の確認 => 存在すればチェック - Cookieが自動で送られないようにする - SameSite 属性の指定 => 抜け穴があるので他の対策と併用 51

Slide 52

Slide 52 text

全体のまとめ - CSRF対策は「リクエストの出どころ」を確認することが大事 - RailsのViewを使う前提ならトークン方式を選べば良い - Rails API であれば Originチェックをベースラインとして考えよう 52

Slide 53

Slide 53 text

補足: 先行発表事例 - 令和時代の API 実装のベースプラクティスと CSRF 対策 - https://blog.jxck.io/entries/2024-04-26/csrf.html - CSRF対策のやり方、そろそろアップデートしませんか - https://speakerdeck.com/hiro_y/update-your-knowledge-of-csrf-protection - PHPerKaigi 2024 53

Slide 54

Slide 54 text

補足: 他フレームワークの対策を調べた - Next.js - https://nextjs.org/blog/security-nextjs-server-components-actions - Hono - ディスカッションの内容が勉強になる - https://hono.dev/docs/middleware/builtin/csrf - https://github.com/honojs/hono/blob/main/src/middleware/csrf/index.ts - https://github.com/honojs/hono/issues/1688 - https://github.com/honojs/hono/pull/1760 54

Slide 55

Slide 55 text

今日の発表者 - @corocn / ころちゃん - 岐阜から来ました - リーナーテクノロジーズ 所属 - nagara.rb 運営(”長良”川 の ながら) 55

Slide 56

Slide 56 text

56 Drinkup、当日空いたら是非・・・!

Slide 57

Slide 57 text

57 もうすぐ 1.0.0 = 開催100回

Slide 58

Slide 58 text

58 長良Ruby会議@岐阜をやるぞ! 来年の夏、鮎が美味しい時期あたり

Slide 59

Slide 59 text

ありがとうございました 59

Slide 60

Slide 60 text

発表後に聞かれた内容 - この対策は MPA(いわゆる普通のRails) でも有効? - 有効です。 - 課題の出発点が SPA + API であるだけで、MPAでも有効。 - MPA は Rails標準のCSRFトークンで保護できるので、触れなかった。 60