Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

Web API連携でCSRF対策がどう実装されてるか調べた / how to implemen...

Web API連携でCSRF対策がどう実装されてるか調べた / how to implements csrf-detection on Web API

OAuth, OIDCを対象に、CSRF対策をどうやって実装しているのか興味がありましたので調査しました。CSRFは昔からあるセキュリティリスクで、普段の開発では意識しなくてもフレームワークやライブラリがよしなにやってくれてますが、「どうやって実装してるの?」と疑問に思う人には有益な内容だと思います。

ぜひ読んでみてください。

面川泰明

June 23, 2022
Tweet

More Decks by 面川泰明

Other Decks in Programming

Transcript

  1. Web API連携で CSRF対策が どう実装されてるか調べた Web API LT会 - vol.4 #webapilt

    | 2022/6/23 | @omokawa_yasu stateの実装を読んでみよう 1
  2. 話すこと 4 1. CSRF攻撃を防ぐ実装 2. Web API連携でCSRF攻撃を防ぐには 3. state 話さないこと

    1. CSRFとは何か 2. OAuthやOIDCなど、Web API連携の仕様 3. nonceなど、state以外の対策方法
  3. 9 攻撃者 被害者 アプリ 認可サービス ② 取得したURLを被害者に送 りつけ、クリックさせる ① 攻撃者が

    認可サービスへ ログイン。 アプリにログインで きるURLを取得 ③ 被害者は意図せず攻撃者の マイページにログインしてしまう。
  4. 10 攻撃者 被害者 アプリ 認可サービス ② 取得したURLを被害者に送 りつけ、クリックさせる ① 攻撃者が

    認可サービスへ ログイン。 アプリにログインで きるURLを取得 ③ 被害者は意図せず攻撃者の マイページにログインしてしまう。 GAME OVER
  5. 12 攻撃者 被害者 アプリ 認可サービス ② 取得したURLを被害者に送 りつけ、クリックさせる ① 攻撃者が

    認可サービスへ ログイン。 アプリにログインで きるURLを取得 送りつけたURLが 攻撃者のセッションで 発行されたことを 検知できればよい
  6. https://github.com/omniauth/omniauth_openid_connect/blob/master/lib/omniauth/strategies/openid_con nect.rb#L241-L250 17 def new_state state = if options.state.respond_to?(:call) if

    options.state.arity == 1 options.state.call(env) else options.state.call end end session['omniauth.state'] = state || SecureRandom.hex(16) end
  7. https://github.com/omniauth/omniauth_openid_connect/blob/master/lib/omniauth/strategies/openid_con nect.rb#L241-L250 18 def new_state state = if options.state.respond_to?(:call) if

    options.state.arity == 1 options.state.call(env) else options.state.call end end session['omniauth.state'] = state || SecureRandom.hex(16) end stateを生成して セッションに格納
  8. https://github.com/rack/rack-session/blob/main/lib/rack/session/abstract/id.rb#L306-L312 21 def prepare_session(req) session_was = req.get_header RACK_SESSION session =

    session_class.new(self, req) req.set_header RACK_SESSION, session req.set_header RACK_SESSION_OPTIONS, @default_options.dup session.merge! session_was if session_was end ※ Sinatraだとこれを使ってた
  9. https://github.com/rack/rack-session/blob/main/lib/rack/session/abstract/id.rb#L306-L312 22 def prepare_session(req) session_was = req.get_header RACK_SESSION session =

    session_class.new(self, req) req.set_header RACK_SESSION, session req.set_header RACK_SESSION_OPTIONS, @default_options.dup session.merge! session_was if session_was end アプリケーションの環境情報に、セッ ション管理オブジェクトを 設定する
  10. https://github.com/omniauth/omniauth_openid_connect/blob/master/lib/omniauth/strategies/openid_con nect.rb#L107-L136 26 def callback_phase error = params['error_reason'] || params['error']

    error_description = params['error_description'] || params['error_reason'] invalid_state = params['state'].to_s.empty? || params['state'] != stored_state raise CallbackError, error: params['error'], reason: error_description, uri: params['error_uri'] if error raise CallbackError, error: :csrf_detected, reason: "Invalid 'state' parameter" if invalid_state ..(省略).. end
  11. https://github.com/omniauth/omniauth_openid_connect/blob/master/lib/omniauth/strategies/openid_con nect.rb#L107-L136 27 def callback_phase error = params['error_reason'] || params['error']

    error_description = params['error_description'] || params['error_reason'] invalid_state = params['state'].to_s.empty? || params['state'] != stored_state raise CallbackError, error: params['error'], reason: error_description, uri: params['error_uri'] if error raise CallbackError, error: :csrf_detected, reason: "Invalid 'state' parameter" if invalid_state ..(省略).. end クエリパラメータのstateと セッションに保存した stateが 不一致ならエラー
  12. 29 攻撃者 被害者 アプリ 認可サービス ② 取得したURLを被害者に送 りつけ、クリックさせる ① 攻撃者が

    認可サービスへ ログイン。 アプリにログインで きるURLを取得 送りつけたURLにstateが付与され ている場合 例) localhost:3000/auth/freee/callback?cod e=2604159252ff006330b8cb632817e537ebcd 567b408611d08bfab87c65b5e1c8&state=33d 2ab070a9ee75bc4fb3169f9bb0c32
  13. https://github.com/omniauth/omniauth_openid_connect/blob/master/lib/omniauth/strategies/openid_con nect.rb#L107-L136 32 def callback_phase error = params['error_reason'] || params['error']

    error_description = params['error_description'] || params['error_reason'] invalid_state = params['state'].to_s.empty? || params['state'] != stored_state raise CallbackError, error: params['error'], reason: error_description, uri: params['error_uri'] if error raise CallbackError, error: :csrf_detected, reason: "Invalid 'state' parameter" if invalid_state ..(省略).. end 被害者のセッションには stateが存在 しないため、不一致と判定される ここで例外が実行される
  14. 34 攻撃者 被害者 アプリ 認可サービス ② 取得したURLを被害者に送 りつけ、クリックさせる ① 攻撃者が

    認可サービスへ ログイン。 アプリにログインで きるURLを取得 送りつけたURLが 攻撃者のセッションで 発行されたことを 検知できればよい
  15. 35 攻撃者 被害者 アプリ 認可サービス ② 取得したURLを被害者に送 りつけ、クリックさせる ① 攻撃者が

    認可サービスへ ログイン。 アプリにログインで きるURLを取得 送りつけたURLが 攻撃者のセッションで 発行されたことを 検知できればよい stateで セッションを 識別すると CSRF対策が 実現できる
  16. 36 参考資料 - CSRFの説明 - Auth屋 『OAuth・OIDC への攻撃と対策を整理して理解できる本(リダイレクトへの攻撃編)』 - CSRF対策の実装

    - https://github.com/omniauth/omniauth_openid_connect - Rackの概念 - https://qiita.com/nishio-dens/items/e293f15856d849d3862b - Rackの動き - https://github.com/rack/rack - https://github.com/rack/rack-session - そもそもセッションとは何か? - https://speakerdeck.com/sylph01/build-and-learn-rails-authentication?slide=30 - 動作確認に使ったWebサービス - https://www.ninja-sign.com わかりやすくておススメです