OAuthと周辺技術の勉強会
OAuthの話。OAuth1.0の脆弱性と1.0aでの対応の話や2-legged OAuthの話まで。JavaScriptのクロスドメイン通信から(なんとなく)Google Friend Connectまで。
OAuthからGoogle Friend Connectまで
はじめに
- 全般的に数年世間から遅れている気がしないでもないですが、気になるので調べてみました。
OAuthの典型的シナリオ
- userがconsumer(Web上のサービス)を利用
- userはSP(別のWeb上のサービス)にアカウントを持っている
- SPは一般にSNSで、userがSP上に蓄積した情報(個人プロファイルや友達リストなど)は原則的にSPの外部に非公開
- consumerは、userに許可を得て、SP上の情報を取得する
- ただし、userはconsumerにSPのパスワードは教えない
OAuth 1.0aの動作シーケンス
表記法
- リクエストとレスポンスの区別は自明ですが、ひとめで分かるようにリクエストは --> 、レスポンスは ==> にしています。
- リクエストやレスポンスペアの上に書いてある数字はOAuth1.0aスペックのセクション番号です。リクエストパラメータやレスポンスの内容はスペックの該当セクションを参照してください。
- リクエストやレスポンスペアの下に書いてある文字列は、そのリクエストやレスポンスでの主な伝達要素です。
- の行は、主にサーバ側(consumerとSPのそれぞれ)の必要な処理と必要な状態管理(何を覚えておく必要があるか)
- 1.0の違いは後述します
事前条件
- consumerは事前にSPに登録してconsumerごとに一意な共有鍵を受け取る
user consumer SP(service provider) -------> 6.1.1 -------> oauth_callback 6.1.2 <======= req-token(unauth) リダイレクト <======= req-token(unauth) 6.2.1 ------------------------> * ユーザ認証 + アクセス許可(grant access) req-token(unauth) * SPは内部でreq-tokenとアクセス許可情報のペアを保持 * oauth_callback先にredirectするので、SPはreq-tokenとoauth_callbackのペアをここまで保持する必要がある * SPがoauth_callbackパラメータを受け取っていない場合、userにoauth_verifierを提示して、userからconsumerに入力させる (e.g. http://code.google.com/apis/accounts/images/OauthUX_nocallback.png) リダイレクト <======================== req-token(authed) + verifier 6.2.3 ------->[callback URL] req-token(authed) + verifier 6.3.1 -------> req-token(authed) + verifier * SPは内部でaccess-tokenとアクセス許可情報のペアを保持 * verifierの値を検証(SPは内部でreq-tokenとverifierのペアを保持しておく必要がある) * req-token(auth)とaccess-tokenの交換は一回のみ 6.3.2 <======= access-token * consumerは内部でaccess-tokenを保持 (必要に応じてaccess-tokenを使ってSPのAPIを叩くと、userが許可したSP上の情報にアクセスできる) <=======
OAuth 1.0の脆弱性
- 前提: req-token(unauth)とreq-token(auth)の値は同じ(単にSPが認証したか否かの違い)
- 攻撃者が、最初のリダイレクト(6.2.1)先のURLを被害者に叩かせる(URLリンクをクリックさせてSP先にログインするようにうまく誘導する)
- 被害者がSPにログインしてアクセス許可をする(のように誘導させる)
- 攻撃者がreq-tokenを覚えておけば、被害者がアクセス許可を出した後、req-token(authed)を使って6.2.3の処理を実行すれば、被害者になりすませる
参考リンク
OAuth 1.0の脆弱性の対応
参考リンク
- http://www.ipa.go.jp/security/fy21/reports/tech1-tg/a_08.html
- http://groups.google.com/group/twitter-api-announce/browse_frm/thread/2086c8c5a12242ec?hl=en
暫定対応(by twitter)
- req-tokenの生存期間を短くした(1.0でも生存期間を持つのがRECOMMENDED。スペック上は1.0でも1.0aでも同じ) => 確率を減らすだけで本質的解決ではない
- oauth_callbackパラメータを無視して、事前に登録済みのURLを使うようにした => 確率を減らすだけで本質的解決ではない
OAuth 1.0aの対応
- 1.0aでは、callback URLを渡すタイミングを変更した => 攻撃者による不正誘導をしにくくする(確率を減らすだけで本質的解決ではない)
- 1.0では、6.2.1でoauth_callbackを渡していた。
- 1.0aでは、6.1.1でoauth_callbackを渡す。
- 1.0aでは、oauth_callback_confirmed(6.1.2)が必須パラメータ => 1.0版consumerを弾くため?
- 1.0aではoauth_verifier(6.2.3と6.3.1)パラメータを追加 => これが解決の本質(6.2.1と6.2.3が同一userであることを保証する)
OAuthの代替?
- OpenID Attribute Exchange 1.0: OpenIDで認証と同時に属性取得(アクセス権限を属性として取得することで、OAuth相当のことが可能)
- AuthSub: Google独自の認証プロトコル(consumerの事前登録不要)
2-legged OAuth (OpenSocial由来?)
- http://oauth.googlecode.com/svn/spec/ext/consumer_request/1.0/drafts/1/spec.html
- 正式名称はOAuth Consumer Request 1.0
- 元々はgadget用?
- consumerとSP間の2者間なので2-legged OAuth (名前がOAuthなだけで全然別物に見える。共通点はパラメータ名にoauth_接頭辞があることぐらい)
- 通常のOAuthを3-legged OAuthと呼ぶ
- Google Appsではドメイン単位の認可?
参考リンク
2-legged OAuthの典型的シナリオ
- SPがgadgetのcontainer、consumerがgadgetの提供サーバ(要は、SPの返すHTML内にconsumerが返すgadgetを表示するiframeがあるイメージ)
- consumerはgadgetをcontainerに登録した時点で、SPから一意な共有鍵を受け取る
- gadgetを追加した時点で、userはconsumer(=gadgetの提供サーバ)にアクセス許可(grant access)をする前提
- SPは内部でgadgetごとにuserとアクセス許可情報のペアを保持
- consumerはSPのAPIを叩く時、ユーザIDを渡す(SPはgadgetの確認(事前に交換した共有鍵を利用)とuserごとの認可チェックを行う)
唐突ですが、JavaScriptのCross Domain通信(iframe利用)
参考:
- WEB+DB PRESS vol.52の富田さんの記事
- http://wiki.developers.facebook.com/index.php/Cross_Domain_Communication
GFC(Google Friend Connect)前提で背景を説明すると
制約
- googleのjsコードをロードしても、HTML(googleではないドメイン)からXHRでgoogleサーバを叩けない
- iframeでgoogleにアクセスしても、親フレーム(googleではないドメイン)からiframe内のjs関数を呼べない
やりたいこと
- googleのjsコードをロードしたHTML(googleではないドメイン)から、AJAX的にgoogleサーバを叩きたい
解決案
- 入れ子のiframe(Facebook Connectパターン。xd_receiver.phpのアップロードが必要)
- facebookサーバにアクセスするiframeが、内部にconsumerドメインのiframeを作り(アクセス先はxd_receiver.php)、xd_receiver.php内のJavaScriptから、window.parent.parent.メソッドを呼ぶ
しかし
- GFCはxd_receiver.phpに相当するファイルがない。どうやっている?
- 同じ疑問: http://stackoverflow.com/questions/1534489/how-does-google-friend-connect-accomplish-cross-domain-communication-without-need
予想
- IE6でCross Domainのサンプルコードが動かないので、単にpostMessageを使っている?
- 親フレームでポーリングすれば、iframe内iframeに特別なコードが不要(画像でもいい)
fyi, 入れ子のiframeの動作シーケンス
user consumer SP(service provider) -------> <======= js:iframe作成 iframeのsrcをSPのURL メッセージ(関数の引数相当)をURLに付与 ------------------------>iframe内 <======================== js:必要ならXHR通信(相手はSP) iframe作成 iframeのsrcをconsumerのURL メッセージ(関数の返り値相当)をURLに付与(#以降に付与するのが定石) ------->iframe内 <======= js:window.locationから#以降のメッセージを取得 window.parent.parent.メソッドを呼び出し、メッセージを引数で渡す
postMessageサンプル
postMessage: JavaScriptのCross Domain通信技術の本命(たぶん)
親から子フレームへメッセージ(関数の引数相当)を送る例
var target = document.getElementById('gadget').contentWindow; // target.postMessage("msg by parent", gadgetUrl); target.postMessage("msg by parent", "*");
子フレームが受け取り、更に親にメッセージ(関数の返り値相当)を返す例
window.addEventListener("message", function(e) { console.debug("iframe recv " + e.origin + ", data = " + e.data); // 必要ならXHR(AJAX)でサーバとやりとり(子フレームと親が異なるドメインの場合、親から見ると別ドメインへのサーバ通信) e.source.postMessage("msg by iframe", "*"); // parent.postMessage("msg by iframe", "*"); }, false);
親のメッセージの受け取りの例
window.addEventListener("message", function(e) { console.debug("parent recv " + e.origin + ", data = " + e.data); }, false);
Google Friend Connect(GFC) API
- http://code.google.com/apis/friendconnect/
- 2-legged OAuthを利用
- blogなどにHTMLの断片をコピーペーストするだけで、blog上にGFCのガジェットを表示可能
- OAuthのシーケンスの用語で説明すると、GFCサーバがSP、blogサイトがconsumer
- GFC(Google)ログイン済みのユーザの情報をconsumerにアクセスさせる
どこからGFC APIを叩くで2つに分類
consumerのサーバ側でSPのWeb API(RESTful)を叩く
- (consumerサイト)cookie値を使う
- jsコードでconsumerサイトのcookie値をセット
- cookie値を、SPのWeb API呼び出し時にパラメータfcauthで渡す
- APIの実行権限はcookie値のユーザ、つまりサイト(blogなど)を見ているユーザ
consumerのJSからSPのWeb APIを叩く