特定条件下におけるOAuth 2.0の認可応答を奪取されるリスクとその対策について

こんにちは、ritouです。
やっと”なんちゃらAdvent Calendar”がおさまり、これからは”一年を振り返って(遠い目”みたいな記事が増えることでしょう。その間のタイミングを狙います。

何の話か

mixi Platformが導入したっていうOAuth 2.0のCSRF対策拡張を使ってみた - r-weblife の最後にちょっと書いたんですけど、モバイルアプリでOAuth 2.0を使う際にやっかいな問題が残ってました。

今回は、「ネイティブアプリケーションからOAuth 2.0を使うとき、特定の条件下において、正規のClientではない悪意のある第3者に認可応答を持って行かれて、その結果Access Tokenを取得できちゃうリスクがあるよね。どうしようか。」っていう話です。

条件っていうのは、

  • OAuth 2.0のClientはネイティブアプリケーションであり、Client Credential(client_id/client_secret)を安全に管理できない
  • Clientは外部のブラウザを立ち上げ、ユーザーは認可要求のURLにアクセスする。この時にいわゆるImplicit GrantやAuthorization Code Grantを使う
  • ユーザーがリソースアクセスを許可した後、認可応答はカスタムURIスキームでClientに渡る

というところです。
なんでこれで認可応答を持って行かれるかというと、カスタムURIスキームの扱いです。
認可要求を送ったClientに認可応答が戻ってくれればよいのですが、カスタムURIスキームを被せてきた(たまたま一緒だっただけかもしれない)別のアプリケーションに認可応答が渡る可能性があると。
で、このあたりのルールが”アプリを選択させる”、“先にインストールした方が優先”、“後にインストールした方が優先”、とかOSに依存するので悩ましい。

認可応答を持っていかれたらどうなるか、

  • Implicit Grant : Access Tokenを取得される -> Scopeで許可されたAPIがたたける
  • Authorization Code Grant : Authorization Codeを取得される -> client_secretがないもしくは安全に管理できない状態なのだからとりあえずAccess Tokenを取得される -> Scopeで許可されたAPIがたたける

で、困ったなーという話です。いつだったかの idconでもこれについて話が出たことがあります。
実直にImplicit Grantなりclient_secretなしのClientを実装したYConnectの担当者にカスタムURIスキームかぶったらどうなるのという質問がありました。

Q: カスタムURLスキームで後勝ち/先勝ちの問題はどう考えているか
- Y!アプリのスキームでは外のアプリとカブらないようにしている → 外にもらさない
- バレちゃって真似されないということ? → Androidではユーザーが選択できる。真似される件は今後の課題
- 外部のアプリではカブってしまう場合がある → 懸念が残る

idcon15レポート - mad-pの日記

YConnectの課題って言うかOAuth 2.0の課題です。

他のところはどうしてるかというと、OAuth 2.0なしくみでAPIを出していてモバイルアプリから使わせてるところで"このあたりを気にしてるところ"は、SDKから公式アプリに問い合わせて、Androidのパッケージ署名やらなんやらを確認した上で認可応答を正規のClientに渡すような仕組みとか、自分たちでいろいろ考えてるわけです。

対策

カスタムURIスキームをどうにかされちゃったとしても正規のClientのみがAccess Tokenを取得できるようにすればいいわけですが、認可応答にAccess Tokenを含んでしまうImplicit Grantではちょっとどうしようもないですね。

なので、Authorization Code Grantをなんとかする方法として2つの拡張機能をざっくり紹介します。

拡張その1 : OAuth Symmetric Proof of Posession for Code Extension

@_nat さんが書いている拡張仕様です。
draft-sakimura-oauth-tcse-03 - OAuth Symmetric Proof of Posession for Code Extension

Clientは2つの値を用意します。

  • code verifier : 認可要求とAccess Token要求を紐づける値
  • code challenge : code verifierの値そのもの、もしくは特定のアルゴリズムを用いたcode verifierのハッシュ値

ざっくり理解してもらうために、code verifier = code challengeとして話を進めます。
認可処理の手順はこうなります。

  • 1. Clientはcode verifierを作成(code challengeも同じ値)
  • 2. Clientは認可要求にcode challengeを含む
  • 3. ユーザーがアクセス許可した後、Serverはcode challengeに紐づけたAuthorization Codeを発行、認可応答に含む。code_challengeは含まない。
  • 4. ClientはAccess Token要求にAuthorization Codeと1で生成したcode verifierを送る
  • 5. ServerはAuthorization Codeとcode verifierの組み合わせが正しいか確認(code challengeとして紐づけてある値とcode verifierを比較する)

図にするとこんな感じすね。汚いけど。

認可応答だけ持っていかれても、Authorzation Codeに紐づいているcode verifierがわからないとAccess Tokenを取得できません。って話です。
さらに、認可要求とアクセストークン要求に含まれる値が同じだとなんかキモいっていうClientとServer同志ならば、code challengeの値をcode verifierのハッシュ値とかにしてもいいよと。

Authorization Code Grantでこの拡張機能を使えば、認可応答を持っていかれても大丈夫です。
ただし、「Clientがちゃんとcode verifierを生成すれば」という条件が付きます。
似たような話、聞いたことないですかね?

拡張その2 : mixi Platformが検討/導入したServer State拡張も対策として使える

CSRF対策!って言ってた拡張機能が、これにも使えます。
書き方をそろえると、

Serverは1つの値を用意します。

  • server_state : 認可要求とAccess Token要求を紐づける値

で、手順は

  • 1. ClientはServerにServer Stateを要求(client_idを指定)
  • 2. Clientは認可要求にServer Stateを含む
  • 3. ユーザーがアクセス許可した後、ServerはServer Stateに紐づけたAuthorization Codeを発行、認可応答に含む。Server Stateは含まない。
  • 4. ClientはAccess Token要求にAuthorization Codeと1で取得したServer Stateを送る
  • 5. ServerはAuthorization CodeとServer Stateの組み合わせが正しいか確認

図とかはこのあたりの記事を見てください。

Serverが値を生成するか、Clientが値を生成するかの違いだけです。
固定のcode challenge送ってくるようなClientのことを考えたら、Server側で払い出す方が良いですよね〜ってことで、個人的にはこっちの拡張の方を推したいところです。

まとめ

  • OAuth 2.0の特定の条件下で認可応答を持っていかれると困るねっていう話の説明をした
  • Implicitじゃなくclient_secretなしのAuthorization Codeなら対策とれる
  • 2つの拡張機能を紹介した
  • YConnectもmixiと同じ拡張機能を導入してはどうか

以上です。

(おまけ)idconで発表したServer State拡張についての説明資料

Idcon 17th ritou OAuth 2.0 CSRF Protection

ではまた!