Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

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

    View Slide

  2. 自己紹介
    2

    View Slide

  3. 3
    副業:占い師
    おもかわ やすあき

    View Slide

  4. 話すこと
    4
    1. CSRF攻撃を防ぐ実装
    2. Web API連携でCSRF攻撃を防ぐには
    3. state
    話さないこと
    1. CSRFとは何か
    2. OAuthやOIDCなど、Web API連携の仕様
    3. nonceなど、state以外の対策方法

    View Slide

  5. CSRF攻撃がどこで起こるか?
    5

    View Slide

  6. 6
    Auth屋 『OAuth・OIDC への攻撃と対策を整理して理解できる本(リダイレクトへの攻撃編)』 より

    View Slide

  7. 7
    攻撃者
    被害者 アプリ 認可サービス
    ① 攻撃者が
    認可サービスへ
    ログイン。
    アプリにログインで
    きるURLを取得

    View Slide

  8. 8
    攻撃者
    被害者 アプリ 認可サービス
    ② 取得したURLを被害者に送
    りつけ、クリックさせる
    ① 攻撃者が
    認可サービスへ
    ログイン。
    アプリにログインで
    きるURLを取得

    View Slide

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

    View Slide

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

    View Slide

  11. 11
    CSRF攻撃を防ぐには?

    View Slide

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

    View Slide

  13. 13
    stateによる対策

    View Slide

  14. 14
    Auth屋 『OAuth・OIDC への攻撃と対策を整理して理解できる本(リダイレクトへの攻撃編)』 より

    View Slide

  15. 15
    実装を追っていく

    View Slide

  16. 16
    ① stateの生成

    View Slide

  17. 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

    View Slide

  18. 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を生成して
    セッションに格納

    View Slide

  19. 認可画面が表示されたときには既に、stateがセッションに格納されてる状態
    19

    View Slide

  20. 20
    セッションの実装

    View Slide

  21. 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だとこれを使ってた

    View Slide

  22. 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 アプリケーションの環境情報に、セッ
    ション管理オブジェクトを
    設定する

    View Slide

  23. ここまでの流れ
    23

    View Slide

  24. 24
    セッション情報を設定したあとで、
    stateを生成してセッションに格納
    ※ 詳しい仕組みを知りたい人は
    (Rack Middlewareで検索)

    View Slide

  25. 25
    ② sessionとstateの対
    応を確認
    ① stateの生成

    View Slide

  26. 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

    View Slide

  27. 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が
    不一致ならエラー

    View Slide

  28. 28
    実際の動きを確認

    View Slide

  29. 29
    攻撃者
    被害者 アプリ 認可サービス
    ② 取得したURLを被害者に送
    りつけ、クリックさせる
    ① 攻撃者が
    認可サービスへ
    ログイン。
    アプリにログインで
    きるURLを取得
    送りつけたURLにstateが付与され
    ている場合
    例)
    localhost:3000/auth/freee/callback?cod
    e=2604159252ff006330b8cb632817e537ebcd
    567b408611d08bfab87c65b5e1c8&state=33d
    2ab070a9ee75bc4fb3169f9bb0c32

    View Slide

  30. 30
    被害者がURLを別ブラウザで開くと ...

    View Slide

  31. 31
    CSRF攻撃だと
    検知され、ログインエラーとなる

    View Slide

  32. 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が存在
    しないため、不一致と判定される
    ここで例外が実行される

    View Slide

  33. 33
    まとめ

    View Slide

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

    View Slide

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

    View Slide

  36. 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
    わかりやすくておススメです

    View Slide

  37. ご清聴ありがとうございました!
    37

    View Slide