はじめに
OAuth2.0の拡張仕様で当たり前になりつつある?PKCEについてまとめました。
「PKCE」とは
PKCEとは、「Proof Key for Code Exchange by OAuth Public Clients」の略称で、認可コード横取り攻撃を対策するための、OAuth2.0の拡張仕様です。
みんな大好き?RFCの7636に定義されています。
RFCに読み方も定義されており、「PKCE」も定義されています。
PKCE, pronounced "pixy"
とあるので「PKCE」は「ピクシー」と読みます。
※ ポケ○ンではありません。
認可コード横取り攻撃
この拡張仕様は「認可コード横取り攻撃」の対策を行うための仕様なので、まず、このRFCで対策する攻撃についてまとめます。
この攻撃にはいくつかの前提があります。
- OAuth2.0の「認可コード」フローを使用
- Publicなクライアントであること
- (基本的には)スマホアプリを想定
1つ目は、「認可コード横取り攻撃」という名前から察するに問題ないかと思います。
2つ目のPublicなクライアントとは、クライアントクレデンシャルを機密に保持できないクライアントのことです。例えば、JavaScriptで書かれたWebページや、ネイティブアプリケーションのように、リソースオーナの手元で実行されるようなアプリケーションのことを言います。これらのアプリケーションは基本的にユーザの手元にアプリケーションのすべてのソースコードがあり、そこにクライアントクレデンシャルも記載されるため機密に保持できません。
3つ目はスマホアプリを想定しているという点です。
これは後ほど説明する認可コードをどうやって横取りするかを説明した後のほうが理解しやすいと思います。
横取り方法
通常の認可コードグラントフローについては下記の図のようになります。
認可コードグラントでは、ユーザが認可した後に③のリダイレクトエンドポイントで認可コードを連携します。
スマホアプリの場合、このリダイレクトエンドポイントは、アプリ実装者が決めるURLスキームに対してリダイレクトします。
iOSでは、同じURL Schemeを持つ複数のアプリを入れることが可能となっています。そのため攻撃対象者のスマホに、悪意のある認可コード横取りアプリを入れることができれば、比較的簡単に認可コードを乗っ取れます。
このように、前提がそろうと認可コードが横取りされてしまいます。
そして、攻撃者のアプリに被害者のトークンが紐付いてしまいます。
横取り対策
横取りされてしまうのは、①でリクエストを行ったアプリと、④でTokenリクエストを行うアプリの検証を行えていないことです。
この2つが同じクライアントから来ていることが確認できれば、悪意のあるアプリに対して、アクセストークンを発行せず、正しいアプリのみアクセストークンを発行することができます。
まず最初に①の認可エンドポイントに送る値にcode_challenge
というHash値を送ります。
hashの元の値code_verifier
を④のトークンエンドポイントで認可サーバへ送り値の検証を行います。
最初の認可リクエストを行ったクライアントとトークンエンドポイントを打鍵したクライアントが同じであることを認可サーバが検証します。
こうすることで、先程の認可コード横取り攻撃者は以下のような状態になります。
最初の認可リクエストを生成したスマホアプリ側でcode_challenge
を生成しているため、横取りをしようとしている攻撃者のアプリは最初のcode_challenge
ハッシュの元になった値を知りません。
故に攻撃者はcode_verifierの検証で失敗し、横取りができない状態となります。
PKCEの限界
ちょっと話がそれますが、PKCEで対策できる範囲には限界があります。PKCEは、攻撃者のアプリが最初のcode_challenge
ハッシュの元になった値を知らないことが前提になっています。
ということは、予測できる値(同じ値を使用する等)の場合は、このフローを使用していても認可コード横取り対策にはなりません。
これはOAuth2.0の限界だと思っているのですが、どんなに最新仕様を認可サーバ側に実装しても、クライアント側の実装に委ねているため、絶対に守れるものでは無いと考えて設計すべきかと思います。
PKCEはCSRF対策になりうるか
さて、話をもとに戻します。
この仕様はもともと
- OAuth2.0の「認可コード」フローを使用
- Publicなクライアントであること
- (基本的には)スマホアプリを想定
が前提となっていましたが、今、CSRFの対策としてPKCEが使用されているところが増えています。
最近のOAuth2.0ために拡張仕様も踏まえて設計しましょうというのが書かれた
Best Current Practice(BCP)というものがあります。
その中に
Clients utilizing the authorization grant type SHALL use PKCE [RFC7636] in order to (with the help of the authorization server) detect and prevent attempts to inject (replay) authorization codes into the authorization response.
although PKCE so far was recommended as a mechanism to protect native apps, this advice applies to all kinds of OAuth clients, including web applications.
認可コードグラントを利用するクライアントは、(許可サーバーのPKCE仕様に助けを借りて)認可コードを認証許可を盗み再利用する試みを検出、防止するためにPKCE を使用すること。
これまでPKCEはネイティブアプリを保護するメカニズムとして推奨されていましたが、このアドバイスはWebアプリケーションを含むあらゆる種類のOAuthクライアントに適用されます。
by Google 翻訳様
とあります。
今まで、CSRF対策として、RFC6749に記載があるstate
で対策するのが一般的でしたが、BPCでは、PKCEを使って対応することも検討しましょうと記載されています。
stateとCSRF対策に関しては過去の私のQiitaが比較的わかりやすいかと思いますのでそちらを参照ください。
OAuth2.0でのCSRF攻撃は上の図でいう、①認可エンドポイントを打鍵したクライアントと③リダイレクトエンドポイントで認可結果(認可コード)を受け取ったクライアントが変わっている事に気づけずにフローが進むのが原因でした。
これが、PKCEを使用したフローになると以下のようになり、攻撃者のアクセストークンが攻撃対象者のクライアントに紐付かなくなります。
故に、PKCEでCSRF対策が行えるのです。
CSRF対策としてのStateとPKCEの違い
となると疑問になるのが、stateとPKCEはどのように違うのか等、疑問が浮かんでくるかと思います。
stateとPKCEの違い、「検証する主体」に違いがあります。
stateの場合、認可後のリダイレクトエンドポイントでクライアントが受け取った「state」を、クライアント側で検証を行っています。
対して、PKCEはトークンリクエストで認可コード共に「code_challenge」を送り、認可サーバ側で値の検証を行っています。
Stateの実装ではクライアント側が正しく実装しているか否かは認可サーバ側が知る由はありませんでしたが、同一トランザクションの検証を認可サーバが行うことにより、クライアント側の実装に依存せずにCSRF対策が行えます。
(とはいえ、PKCEの限界で述べているような場合が存在するので完全にCSRF対策が行えるわけではないです。)
まとめ
PKCEで対策できる認可コード横取り攻撃から、CSRF攻撃の対策となるところまでまとめました。
OAuth2.0を本格的に勉強し始めたときに、「PKCE」ってなんや!「state」とは違うんか!
と思っていたので、違いに悩んでいる方に理解してもらえれば幸いです。