Edited at

OpenID Connectのstateとnonceの違いがわからなかった

OpenID ConnectのRPを実装していた際、Authentication Requestでのパラメーターにstateとnonceの2つがあることに混乱してしまったので自分用まとめです。防ぎたい攻撃はそれぞれ別のようですがnonceだけで良くないか?と。


OpenID ConnectはOAuthの拡張

OpenID ConnectはOAuth 2.0でAPIを保護しつつ、クライアント上でのユーザー認証もできるようにした仕様です。そしてstateはOAuth 2.0由来のパラメーターで、nonceはOpenID Connect由来のパラメーターです。あくまでOpenID ConnectはOAuth 2.0の上に乗っかっている存在のため、たとえnonceでカバーできようとも互換性のためstateには手を出さなかったと考えることで似たようなパラメーターがある意味に納得することにしました。

また出自の違いから守りたいものが違う、とも考えました。OAuth 2.0由来のstateはアクセストークンを守りたい、OpenID Connect由来のnonceはID Tokenを守りたい。

納得できたのは良いものの具体的にどんな脅威があるのかも気になり調べていくことにしました。ちなみにすべて認可コードフローを前提にしています。


stateで防ぎたい攻撃の例

stateはOAuth 2.0由来のパラメーターなので守りたいものはアクセストークン、およびそれによってアクセスできるリソースです。具体的な脅威についてRFC68191に載っていたので引用します。


影響: 被害者は攻撃者の代理としてリソースアクセスを行う. その栄養はアクセスされるリソースに依存するが, 例えば被害者が攻撃者のリソースとしてプライベートなデータをアップロードするケースなどが考えられる. OAuthを3rd partyログインの為に用いている場合, クライアント上の被害者アカウントが外部Identity Provider上の攻撃者アカウントと紐づけられてしまい, 攻撃者が別デバイス上で被害者としてクライアントにログインできるようになってしまう, といったケースも考えられる.


2つの例が載っています。今回はOpenID Connectを利用するので後者の3rd partyログインは考えないこととし、前者の例で考えていきます。


  • 攻撃者がしたいこと: 被害者のプライベートな情報を入手したい

  • 攻撃方法: CSRFにより攻撃者のリソースにアクセスできるアクセストークンを被害者に取得させ、プライベートな情報をアップロードする可能性をつくる

フローに起こしてみました。攻撃者自身の認可コードを被害者に使わせ、攻撃者のリソースにアクセスできるアクセストークンを取得させる。被害者はプライベートなファイルを攻撃者のリソースとしてアップロードしてしまいます。

oidc-fig1.png

これを防ぐためにstateを利用します。


  1. 認可リクエスト時にstateを生成し認可サーバーへ送ります

  2. 認可サーバーはクライアントに認可コードを渡す際、リクエストで送られてきたstateも渡すようにします

  3. クライアントはstateがリクエスト時の値と一致しない、もしくは渡されてこなかった場合にトークンリクエストを行わないようにします。


nonceで防ぎたい攻撃例

OpenID Connect由来のパラメーターなので守りたいものはID Tokenです。nonceはリプレイアタック対策で用いられるものと仕様書2に書いてあるのでまずはリプレイアタックが何なのかを調べました。


アリスが自分のアイデンティティをボブに対して証明することを仮定する。ボブはアイデンティティの証拠としてアリスのパスワードを要求し、アリスは忠実にパスワードを提供する(おそらくはハッシュ関数のような若干の変換をして)。一方でイブは会話を盗み聞きしてパスワードを記録する。二人のやり取りが終わった後で、イブはアリスのふりをしてボブに接続を行う。アイデンティティの証明を要求されるとき、イブはアリスの前のセッションから読み取ったパスワードを送り、ボブはこれを受け入れることになる。

反射攻撃 - Wikipedia」(2016年7月29日 (金) 19:36 UTCの版)より引用


ログイン時に送られるデータを攻撃者が記録し、それをそのまま再現することで不正にログインするということのようです。

OpenID Connectで考えると認可サーバーからID Tokenが送られてくるやりとりを記録し、不正ログインを試みるということでしょうか。フローに起こしてみます。

oidc-fig2.png

これを防ぐためにnonceを利用します。


  1. Authentication Request時にnonceを生成しIdPに渡します

  2. IdPはID Tokenの中に渡されたnonceを含めます

  3. クライアントは自らのAuthentication Requestに対してのID Tokenであることを確認するため、リクエスト時に渡したnonceとID Tokenに含まれるnonceが同一かどうか比較します

ただID TokenのやりとりはIdPとRPによるバック・チャネル・コミュニケーションなので攻撃難度は高そうだなと思いました。仕様上でも認可コードフローの場合はOPTIONALになっています。

一方でインプリシットフローの場合はREQUIREDです。3

これはオープンリダイレクトによってトークンが漏れる4、クライアントに渡すトークンを手元で置き換えられる等が理由かなと想像しています。(オープンリダイレクトを用いた具体的な攻撃手順については「OAuth徹底入門5」で読んだままを書くことになってしまうため詳細は控えます)

しかしインプリシットフローが対象にしている「ブラウザ内だけで動作するJavaScriptアプリケーション」上でトークンを検証する意味はどれくらいあるのでしょうか。ノーガードよりマシなのかもしれませんが攻撃者はいくらでも手元で処理をスキップできる気がします。

事例を探していたらバックエンドサービスがIdPと直接やり取りできない場合にインプリシットフローを使うという例6に出会いました。このパターンでは、まずnonceをサーバー上で生成し保存、そしてクライアントが受け取ったID Tokenがバックエンドサービスへ投げられると保存しておいたnonceとID Tokenに含まれるnonceとで同一かどうか比較するという感じのようでした。これならわかる気がします。


まとめ


  • OpenID ConnectはOAuth 2.0の拡張です

  • stateはOAuth 2.0由来のパラメーター、nonceはOpenID Connect由来のパラメーター、出自が違います

記載の脅威はあくまで一例に過ぎず、実際は様々な脅威も同時に防いでいます。そこが混乱の元だったのですが、まずは自分なりに基本の使われ方を定義することでその他の脅威も理解しやすくなりました。