ログイン中のアカウントと紐付けたOAuthのCSRF対策について
ritouです。これは何のアドカレの何日めでもありません。
普段から、OAuth 2.0やOIDCあたりのCSRF対策漏れってのを見かけた場合、報告したりしています。
いわゆるstateパラメータを使っていなかったり、雰囲気で認可リクエストに使っていると見せかけて検証していないケース*も見受けられます。
今回はそれらよりも一歩進んだ話で、どことは言いませんがこんな事案がありました。
- ログイン状態でサービス連携するところでOAuth 2.0が使われている
- stateは使っている
- stateにはセッションに紐づく値ではなく、ユーザーID+時刻あたりをJWT形式で含んでいる(今はJWEで見えないが前はJWSで見えた)
こういう実装ってどうなのかについて自分の考えをまとめておきます。
ユーザーと紐づけられたstateにより何を守れるか
OAuth2.0におけるCSRF攻撃とは 「攻撃者が自らのセッションに紐づいた認可レスポンスを第3者に処理させる」 ものです。
対策として、認可リクエストのstateパラメータにセッションと紐づく値を指定し、認可レスポンスに含まれる値を検証することが仕様にも書かれています。
今回のように必ずログイン状態で行われる機能であるという前提を追加すると、攻撃者のユーザーIDに紐づいたセッションに紐づいた認可レスポンスを第3者が処理できてはいけないということになります。
そのために、今回のような ユーザーIDに紐づけられたstate を利用することにより、別のユーザーに紐づいた認可レスポンスを受け入れないという対策が行われていると言えるでしょう。実質これで問題はなさそう。
発行時刻を含むJWT
今回のstateの値には認可リクエストを生成した時刻と思われる値が入っていたので、それを検証することでstateの値が有効なものとして扱われる時間を制限できます。
上述の通り同一ユーザーのみ、さらにこの時間だけ使えるstateの値を使うことでCSRF対策をしているという主張もわからんでもありませんし、実際は確かに有用でしょう。
時刻の話やワンタイムとかをCSRF対策に入れるとちょいとややこしくなるので、一旦これはこれで良いとおいときます。
セッションと紐づけられたstateとの違い
大抵のWeb Application Frameworkでは "未ログイン状態" でも "セッション" が発行され、例えばログインフォームのCSRF対策などで利用されます。
例えば、同じユーザーでログイン状態となっている複数のWebブラウザではログイン中のユーザーIDの値は同じですが セッションID" の値は別になるでしょう。また、ログアウトの際にセッションを更新してセッションIDを再発行する実装というのも割と一般的かと思います。
セッションに紐づけられたstateの値を使うことで、ユーザーに紐づけられたstateよりも利用できる範囲をより狭く留めておけますし、未ログイン状態でのOAuth/OIDC利用時にも利用できます。
例えば
- ブラウザの通常のWindowとシークレットモード/プライベートモードなWindowの両方でログイン状態になる
- 通常のWindowで連携処理を行い発行された認可レスポンスをシークレットモードで実行する
ということをやると、ユーザーと紐づけられたstate であれば ユーザーIDが一致するので認可レスポンスは処理され、セッションと紐づけられたstate ではエラーと判定されます。
ログイン状態必須な機能かどうかによって設計を変えても想定している攻撃への対策ができていれば良いわけですが、このあたりは脳死で とりあえずセッションに紐づけておく と覚えておくで良いのではと個人的には思います。
Discussion