こんにちは、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スキームで後勝ち/先勝ちの問題はどう考えているか
idcon15レポート - mad-pの日記
- Y!アプリのスキームでは外のアプリとカブらないようにしている → 外にもらさない
- バレちゃって真似されないということ? → Androidではユーザーが選択できる。真似される件は今後の課題
- 外部のアプリではカブってしまう場合がある → 懸念が残る
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側で払い出す方が良いですよね〜ってことで、個人的にはこっちの拡張の方を推したいところです。