2月第3週の振り返り

2月第3週(2/10-2/16)

ASP.NET Core MVC

SignalR以前にFitbit APIの利用に必要なOAuth2.0を理解していないことに気づき調べまわっていた。授業プロジェクトではNuGetのFitbitクライアントパッケージを利用していたが更新されておらず(当時はASP.NET 4で開発していた)、自前で実装するためにFitbit APIリファレンスを読んだところ以下のようなフローであることが分かった。

  1. ユーザをFitbitの/oauth2/authorizeにアクセスさせる。クエリストリングにはresponse_type=code, client_idおよびscopeとstateを指定する

redirect_uriはFitbit連携アプリの管理画面で単一のものを設定している場合は含める必要がない。複数URIを管理画面で指定している(改行で区切る)場合はそのどれかに完全一致する必要があるようだ。そのためオープンリダイレクトの危険性はなさそうであるが、必要がなければ本番環境用クライアントのリダイレクトURI設定は単一にして、クエリストリングには含めないでおくのがよいだろうと思った。

  1. ユーザが自サイトのアプリケーションによるFitbit個人データへのアクセスをAuthorize(認可)したら、Fitbit側が指定のリダイレクトURI(自サイト)にトークン要求用コードをクエリストリングとして付してユーザをリダイレクトさせる
  2. アプリケーションの側で、このリダイレクトによって発生したGETリクエスhttps://[mysite]/my_callback_uri?code=...からクエリストリングを読み取り、トークン要求用コードを取得する
  3. アプリケーションはFitbitの/oauth2/tokenに対しAuthorization: Basic base64_encode("client_id:client_secret")ヘッダー、client_id=XXXXX, grant_type=authorization_code, redirect_uri, code=トークン要求用コードからなるx-www-form-urlencodedなボディでPOSTリクエストを送る
  4. API利用のためのトークン・リフレッシュトークン・認可された権限リストが入ったJSONレスポンスが返される
  5. (必要な権限が認可されていなければ自サイトからユーザにエラーを出す)
  6. Authorization: Bearer トークン の方式で各種APIにリクエストを送る

これがAuthorization Code Grant Flowと呼ばれる方式であるらしい。この方式ではトークン要求用コードはワンタイム(Fitbitの場合)かつ短寿命、トークンも短寿命であるためセキュリティリスクが少ないとされる。

ただし、リダイレクトをブロックしてしまえば未使用のトークン要求用コードを(短寿命であるとはいえ)取得できてしまう。害意を持った人間はこれを利用し、被害者に3.からステップを始めさせるようなリンク、すなわちhttps://[mysite]/my_callback_uri?code=valid_unused_codeを送るか、あるいは攻撃者が運営するサイトや脆弱性のある第三者掲示板などでimgタグのsrc属性に上記リンクを設定したり、HTTPリクエストを送信するようなJavaScriptを埋め込んだりすることで3.のGETリクエストを発生させること(CSRF)ができてしまう。その結果、4.で攻撃者アカウントデータへのアクセスを被害者に認めることになる。もし私のアプリケーションがFitbitプロフィールの変更機能を提供していたとすれば、被害者が自分のアカウントだと誤解して自分の誕生日や名前を攻撃者のアカウントに設定してしまうことが起きうる。

Fitbitは幸いにもデータを書き込むリクエストが限られており、位置情報を含むアクティビティデータはFitbit端末から公式アプリ経由でしかアップロードされないので致命的ではないとはいえ、攻撃者アカウントへの書き込みを被害者に許可するという構図が一般に危険であるということは認識する必要がある。

これを防ぐため、1.の段階でクエリストリングにstate=ランダムで推測困難な文字列を付すということが行われる。そしてユーザがFitbitの認可画面に飛ぶ前に、自サイト側でユーザに対応するサーバ側セッションにその値を記録しておく。

こうするとFitbit側認可ページにアクセスした本人=攻撃者に対応するサーバ側セッションにしかstateは付かない。Fitbit側はリダイレクトの際codeとともにstateもクエリストリングに付してくれるので、GETリクエストに含まれるstateクエリストリングと、アクセス者に対応するサーバ側セッションに記録されたstateの値とを照合し、合致しなければ4.の処理に移らないという対策を自サイト側で講じることができる。

stateが全員共通だったり、推測可能だったりした場合、アクセス者=被害者に対応するサーバ側セッションのstateと、攻撃者が推測して改変したURL中のstateが一致してしまうので対策にはならない。

またstateをセッションIDそのものにしてしまうと、3.のページ中で外部のjs(bootstrapなど)や画像(広告バナーなど)を使用していた場合、それらを要求する際のリファラにセッションIDが載ってしまうので望ましくない。

人間の害意を想定して対策を講じるにはこのように様々な考慮が必要であり、素直な認可に比べて面倒なものとなることがよく分かる。