GoogleのOAuth 2.0実装におけるToken置換攻撃の防ぎ方

こんばんは, ritouです.
OAuth.jpのnov先生のポストにもある通り, OAuthのImplicit GrantにおけるToken置換攻撃の話や対策についてはFacebookが話題の中心だったりします.
この前Google+の新機能について調べたついでにGoogleのImplicit周りの実装について確認したので, GoogleのOAuthにおいてToken置換攻撃をどのように防ぐべきかを共有します.
(中の人でも啓蒙する立場でもなく勝手に調べただけなのであしからず)

ClientがWebServerを利用しない場合 : Access Tokenの内容を確認するAPIをたたく

Token置換攻撃への対策として必要なのは, Clientが受け取ったTokenが自分用に発行されたものかどうかの確認を行うことです.
Googleの場合, Access Tokenの中身を確認するEndpoint(API)が提供されていますので, それをたたいてAccess Tokenの内容を確認します.

If your application uses the user agent flow, it MUST validate the token. Failure to verify tokens acquired through the user agent flow make your application more vulnerable to the confused deputy problem. If your application is using the Web Server flow, then there is no need to explicitly validate the token.

You can validate a token by making a web service request to an endpoint on the Google Authorization Server and performing a string match on the results of that web service request.

OpenID Connect  |  Google Identity Platform  |  Google Developers

Access Tokenを以下のエンドポイントに投げることでそのAccess Tokenの内容を確認できます.

https://www.googleapis.com/oauth2/v1/tokeninfo?access_token={accessToken}

レスポンスはこんな感じになります.

{
  "audience":"8819981768.apps.googleusercontent.com",
  "user_id":"123456789",
  "scope":"https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email",
  "expires_in":436
}

Clientはこのaudienceという部分が自分自身のClient IDになっていることを確かめることで, 自分に対して発行されたものであることを確認できますね.

ClientがWebServerを利用する場合 : "response_type=code token"を用いる

OAuth 2.0の仕様を知っている方は, リソースオーナーの認可を得るためのリクエストのresponse_typeパラメータに次のような値が入ることを知っているでしょう.

レスポンス
code Authorization Codeがクエリパラメータとして返される
token Access Tokenがフラグメントとして返される

Googleさんは、以下のようなresponse_typeにも対応しています.

レスポンス
id_token 認証イベントの結果を含むID Tokenがフラグメントとして返される
code id_token Authorization CodeとID Tokenがフラグメントとして返される
code token Authorization CodeとAccess Tokenがフラグメントとして返される
id_token token ID TokenとAccess Tokenがフラグメントとして返される
code id_token token Authorization CodeとID TokenとAccess Tokenがフラグメントとして返される

何を言ってるかわからないかもしれませんが, OpenID Connectでは認証イベントの内容をID Tokenとして渡すことができ, response_typeはcode, tokenという値と組み合わせることもできます.
GoogleさんはOpenID Connectのresponse_typeをフルサポート済みなのです.ただし, ID Tokenのことは一旦おいておきます.

ここでnov先生が対策として述べられているように, ここでは"response_type=code token"を用います.

プラットフォームの iOS SDK は “response_type=token code” に対応する。
被害アプリは、"response_type=token code" を使い、サーバーサイドへは access token ではなく authorization code を送る。(そしてサーバーサイドでは client authentication して code を token と交換する)

http://oauth.jp/-ios-sdk

ここで, nov先生はClientでもバックエンドのWebServerでも両方でAPIをたたくようなアプリを想定しているんだと思います.

  • Authorization CodeとAccess Tokenのセットを受け取るよ
  • 自分に対して発行されたかどうかをWebServer側でClient Credential + Authorization Codeで確認するよ
  • WebServer-Client間でもAccess Tokenが流れないので途中であれこれされるリスクもないよ

まぁ、厳密にはこれでも置き換えられる可能性はあります.
別のClient向けのAccess Tokenと正しいClient向けのAuthorization Codeを並べてアクセスすることも不可能ではありません.
よって、ここでもClient側で受け取ったAccess Tokenはさきほど紹介したAPIを利用たたいて自分向けのものか確認する方が良い気がしますね.

ここまでで言いたいことは終わっています.
あとはおまけです.

OpenID Connect正式対応を発表したらID Tokenの値を利用して検証可能

上記のとおり, GoogleOpenIDへの対応を"かなり"進めています.
上記の表に出てきた, ID Tokenの中身を紹介しましょう.
ID Tokenは"ヘッダ.ペイロード.シグネチャ"という構成になっており, ヘッダとペイロードの内容はこのような感じです.

ヘッダ : 
  "alg":"RS256"
ペイロード
  "iss":"accounts.google.com"
  "aud":"286987262197.apps.googleusercontent.com"
  "cid":"286987262197.apps.googleusercontent.com"
  "id" : "114181308725730985237"
  "token_hash":"v2ocupJl_lTYSHsp0_KEJA"
  "code_hash":"EjTThLRvg2IAi1tWkd25bA"
  "iat":(発行時のタイムスタンプ)
  "exp":(有効期限のタイムスタンプ)

細かい名前など最新仕様とあっていない部分はあるのですが,

  • iss, audで発行元, 発行先の確認
  • iat, expで発行日時などの確認
  • token_hash, code_hashでAuthorization Code/Access Tokenと一緒に返されたときの組み合わせの確認

が可能です.便利ですね.

なぜ今使えないのかというと, ヘッダ部分にalg=RS256とあるので, RSA using SHA-256 hash algorithmを用いたシグネチャがついていることになります.
しかし, 今のGoogleさんはこの公開鍵のありかがわからない...ので署名検証ができず, 使えないということです.
このあたりがDiscoveryで取得できるようになると, ID Tokenの検証をするだけでAccess Tokenの検証, Authorization Codeとの組み合わせなども確認できるようになります.
Googleさんの今後に期待したいところであります.

おまけ:G+では独自のJS実装をおすすめしている?

G+ではClient-side flowとして、Google提供JavaScriptを使ったAuthorization処理を紹介しています.
Google+ Platform  |  Google Developers

ここでダウンロードして使えと言われている"client-side flow starter project"を自分のVPSにおいてみたのがこちらです.
(こちらもDeveloper Previewに参加してログイン中でないとSign Inボタンが表示されないかもしれません)
http://www8322u.sakura.ne.jp/client-side-starter/index.html

ここでクリックするとポップアップが開いてGoogleにOAuth 2.0におけるImplicit Grantのリクエストが飛ぶことを予想します.
HTTP Headerを見守ったところ, こうなりました.

https://accounts.google.com/o/oauth2/auth
?client_id=286987262197.apps.googleusercontent.com
&redirect_uri=postmessage
&response_type=token%20id_token
&state=1355247739
&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fplus.moments.write%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fplus.me
&Hq=en-US&origin=https%3A%2F%2Fplusone.google.com
&proxy=oauth2relay570462233&authuser=0

ここで注目なのが以下のパラメータです.

  • redirect_uri : postmessage

redirect_uriにはURIが指定されるべきなのにpostmessegeとはよくわかりません。
その後, Googleドメインのページが間に入ってTokenをやりとりしているようです.

https://plusone.google.com/_/widget/render/plus?... (以下略)

このような戻し方はOAuth 2.0では見たことがありません.
"OPのJSを信頼して"インクルードにより実現できるならExtensionとして面白いかもとか思いますがこれ以上は追わないことにします。

今日はここまでです.
長文を読んでいただきありがとうございました.

ではまた!