OAuth 2.0 / OpenID Connectにおけるstate, nonce, PKCEの限界を意識する

f:id:ritou:20190708035757p:plain

おはようございます、ritouです。ちなみに予約投稿なのでまだ寝てます。

本日のテーマはこちらです。

OAuth 2.0で言うところのClientの視点から、ここに気をつけて実装しましょうという話ではありません。

OAuth 2.0で言うところのServerの視点からみて、Clientにこんな実装されたらたまんねぇなっていうお話です。

最終的には一緒な気もしますが、とりあえず始めます。

state

OAuth DanceにおけるCSRF対策としての state パラメータについて簡単に整理します。

  1. Clientがセッションに一意に紐づく値として生成、管理
  2. ClientがAuthorization Requestに付与
  3. ServerはAuthorization Requestとして受け取った値をAuthorization Responseにそのまま付与
  4. ClientはAuthorization Responseとして受け取った値を検証

しかし、Clientに次のような実装をされると、state の意味が無くなります。

  • セッション問わず常に同じ値を指定する
  • 実は検証してない

これらをServer側で防ごうとしても、限界があります。

  • state の検証まで行うSDKを提供しても使われないかもしれない
  • AuthZ Requestに state の付与を必須にしても、ワンタイムにしろと定義されているわけでもないためキャッシュして弾くような実装にもできない
  • 検証しているかどうか、Webアプリケーションからの利用などはプラットフォームから配信されるネイティブアプリのように審査で完全にチェックできるわけでもない

Client側の実装が正しく行われなくても処理が完結できる作りになっている以上、Serverとしては手が出せません。

nonce

OIDCの nonce パラメータ、ご存知でしょうか?なんか聞いたことある? なんとかPayの前にちょっとだけ話題になってた「Sign-In with Apple」の実装とOpenID Connectの仕様の差異についての記事を目にされた方もいらっしゃるでしょう。

japanese.engadget.com

nonce 扱いが、この件の仕様の差異として出てくるのですが、一旦整理します。

  1. ClientがAuthZ Requestを送る際に生成、管理
  2. ClientがAuthorization Requestに付与
  3. ServerはAuthorization Requestとして受け取った値をAuthorization Response/Access Token Responseに含まれるID Tokenに含む
  4. ClientはAuthorization Response/Access Token Responseに含まれるID Tokenを検証

ID Tokenに含まれているので、Clientがネイティブアプリ-バックエンドサーバーと言う構成になっていたりしても値を引き継いで検証可能です。

当然こちらも、Clientに次のような実装をされると意味が無くなります。

  • 同じ値が指定される
  • 実は検証してない

これらをServer側で防ごうとしても、state と同様に

  • nonce の検証まで行うSDKを提供しても使われないかもしれない
  • AuthZ Requestにnonceパラメータの付与を必須にして値のハッシュ値とかをキャッシュして弾いてやろうと思っても色々悩む
  • 検証しているかどうか(以下略

となります。

個人的に、キャッシュするとかどうとかのあたりはライブラリで実装しても良いかなと思った時期がありましたが、

  • フォーマット自由だしな...ハッシュで保持するしかないか?
  • いつまで保持するの?データ量...たくさんのClientからリクエストが来るServerだったら...

など、得られる効果に対してなかなか悩みどころが多そうな感じです。 と、これを書きながら、ふと思い出しました。 10年以上前の2007年だか2008年に策定されたOpenID Authentication 2.0っていう仕様では、ここで言うAuthZ ResponseにServer側がnonceを払い出す、かつフォーマットの指定まで定義されていました。Final: OpenID Authentication 2.0 - 最終版

openid.response_nonce 値:長さが 255 文字以下の文字列で、この成功した特定の認証応答に固有のものでなければならない (MUST)。ノンスは、サーバの現在時刻で始まらなければならない (MUST)。またノンスには、それぞれの応答を固有のものとする上での必要に応じて、33-126 の範囲に含まれる ASCII 文字 (空白を除く印刷可能な文字) を追加してもよい (MAY)。

その上で、Client側でもちゃんと検証しろと書かれていました。

"openid.response_nonce" について、当該 OP から、これまでに同じ値のアサーションを受け入れたことがない

今回のnonceパラメータもこれぐらい厳密であれば、Server側で管理できなくもない気もしていますが、現状はやはりClient側の実装が正しく行われなくても処理が完結できる作りであることを受け入れざるを得ません。

ちなみにOAuth 2.0/OIDCよくわかってないけどWebAuthnわかる方は、WebAuthnのフローに出てくる challenge パラメータをイメージしていただけると良さそうです。

WebAuthnでもClient/Authenticatorは手元のブラウザ、セキュリティキーですし、challengeの値をキャッシュして重複を弾く実装を確実に行うのは難しそうな印象です(詳しい人の意見求む)。

PKCE

最近のOAuth/OIDCの議論や実装でよく見かけるようになったPKCEについても整理します。

  1. ClientがAuthZ Requestを送る際に code_verifier, code_challenge, code_challenge_method を 生成、管理
  2. ClientがAuthorization Requestに code_challenge, code_challenge_method を付与
  3. ServerはAuthorization Requestとして受け取った値をAuthorization Codeに紐付けておく
  4. ClientはAccess Token Requestに code_verifier を付与
  5. ServerはAuthorization Codeとcode_veirifier を検証

d.hatena.ne.jp

state, nonceと一緒なのは、Clientが生成するってところです。 state, nonceと異なるのは、Serverが検証するし、その検証をしないと処理が完結しないところです。

と言うことで、Clientに次のような実装をされると意味が無くなる点としては。

  • 同じ値が指定される

ぐらいでしょうか。

  • パラメータ生成機能を持ったSDKを提供しても使われないかもしれない
  • AuthZ Requestへのパラメータの付与を必須にして値のハッシュ値とかをキャッシュして弾いてやろうと思っても(略

と言うあたりは残ります。

まとめ

ここまでを一旦まとめると

  • Clientが生成するものをServerは完全には検証できない : 細かなフォーマットがあればなんとかなるかもしれないが...
  • Clientが検証しなくても処理が完結してしまうものに対してServerは手を出せない

と言う当たり前のお話でした。

今回、なぜこれを書いたかと言うと世界平和のためにTwitterしてたら急に思い出しました。

もう6年前の話なので忘れかけていますが、某プラットフォームでOAuth 2.0(RFC6749/6750)に続いてOpenID Connectの仕様の最低限のところを実装し、これで「〜でログイン」させたいというのを内部の然るべきルートで相談したところ、当時大先輩と呼んでいたセキュリティな人に上述の state の話をされました。 セキュリティレビュー、ちゃんと機能してた!(大事)

ID連携をグループ内だけで展開するので Client のチェックができる環境、SDK利用を強制できるなどの場合はまた別だったかもしれません。 しかし、一般開発者向けに提供される仕組みにおいて、全てのClientの実装を継続的にチェックする/できるような状況にならない限りは許容できない→確かにそうですねという感じで、対策として独自の拡張機能を実装することになりました。

そして独自拡張というのは、PKCE で言う所の code_challenge_method=plaincode_verifier(=code_challenge) を Authorization Server が発行してワンタイムになるように管理するものです。

alpha.mixi.co.jp

  • Client が ServerState を要求し、 Authorization Server が Client と紐付けて発行
  • Client は AuthZ Requestに含み、Authorization Serverは ServerState の有効性、紐付けられている Client を検証
  • Authorization Server は Authorization Code と紐付け、AuthZ Response には含まれない
  • ClientはAccess Token Request に Authorization CodeとServer Stateを含む
  • Authorization Server は それらを検証

code_challenge_method=s256 のようにしようと思ったら、それもできます。 今思うと新しいEndpointを増やしたくなかったのでToken Endpointから返してますが、この用途なら新しいエンドポイントで返すべきだと思っています。 そして、ここから先(標準化に向けた取り組み)をせずに野良のまま放置してしまったのも私の怠慢です。

ちなみに相談の時に nonce の話をしたかどうかは忘れましたが、PKCEが出た後だとしても同様の展開になったと思います。 と、言うことで、最後にもう一度まとめます。

  • state, nonce, PKCEがうまく機能しなくなるClient側の実装を意識しよう
  • 完全ではないということでこれらの仕組みがダメだという話ではなく、リスクをどの程度まで軽減させ、受容できるのかを考えるのが大事でしょう
  • 自分の場合はセキュリティ担当からの指摘によってServerStateと言う独自拡張が爆誕した過去の思い出

以上です。今週も頑張りましょう。

ではまた。