読者です 読者をやめる 読者になる 読者になる

mixi engineer blog

ミクシィ・グループで、実際に開発に携わっているエンジニア達が執筆している公式ブログです。様々なサービスの開発や運用を行っていく際に得た技術情報から採用情報まで、有益な情報を幅広く取り扱っています。

OAuthのセキュリティ強化を目的とする拡張仕様を導入しました

OAuth platform security

こんにちは. 研究開発グループ ritouです.

だいぶ前の記事で紹介したとおり, mixi Platformは様々なユーザーデータをAPIとして提供するにあたり, リソースアクセスの標準化仕様であるOAuth 2.0をサポートしています.

mixi PlatformがOAuth 2.0の最新仕様に対応しました | mixi Engineers' Blog

mixi Platformをさらに安全にご利用いただくため, OAuth 2.0におけるCSRF対策を目的とした拡張仕様を検討, 導入しましたので紹介します.

OAuth 2.0の認可フローとCSRF

エンジニアの方であれば, Webサービスに対するCSRF(Cross-site Request Forgery)をご存知でしょう.
CSRF攻撃とは悪意のある外部サービスへのアクセスや特定のURLへの誘導などをきっかけとしてユーザーの意図しない機能を実行させることを目的とする攻撃であり, 日記などの投稿フォームにてCSRF対策を怠ると被害者が意図しない書き込みを許すなどのリスクがあります.

このCSRFとの関係の前に, OAuth 2.0の認可フローを簡単に説明します.
OAuth 2.0の認可フローでは, APIを提供するサービス(以下, OAuth Server)とAPIを利用するサービス(以下, OAuth Client), 利用者のUserAgent間で次のようなやりとりが発生します.

OAuth 2.0の認可フロー

  1. OAuth ClientはOAuth ServerにWebブラウザのリダイレクトを用いて認可要求を送る
  2. OAuth ServerはOAuth Clientがアクセスを求めているユーザーデータについてユーザーに説明し, アクセス許可を得る
  3. OAuth ServerはOAuth ClientにWebブラウザのリダイレクトを用いて認可コードを含む認可応答を返す
  4. OAuth Clientは受け取った認可コードを用いてOAuth ServerにHTTPリクエストを送り, アクセストークンを取得する

OAuth Clientは上記の手順で取得したアクセストークンをユーザーAのWebブラウザが持つセッション, もしくはユーザーAに紐づけて管理するのが一般的です.
この認可フローに対するCSRF攻撃について説明します.

OAuth 2.0の認可フローにおけるCSRF

ユーザーAはOAuth Server上でリソースアクセスを許可した後, 手順3 で認可コードを含む認可応答を自らのWebブラウザにOAuth Clientに送らず, なんらかの方法でユーザーBのWebブラウザに認可応答を処理させます.
認可応答を受け取ったOAuth Clientが手順4で認可コードからアクセストークンを取得できた場合, ユーザーBのWebブラウザのセッションもしくはユーザーBと”ユーザーAのアクセストークン”を紐づけます.

このような状態では, ユーザーBが投稿した内容はユーザーAのリソースとして扱われます.
ユーザーAはユーザーBのプライベートな情報にアクセスできる状態になったり, OAuthを認証用途に利用している場合はユーザーBのアカウントがユーザーAにより乗っ取られる可能性もあります.
私たちはこれをOAuth 2.0におけるCSRFのリスクととらえています.

既存の対策と問題提起

一般的なWebサービスにおけるCSRF対策としては, HTMLフォーム送信時にセッションIDなどをパラメータに含みデータを受け取る際に検証する方法が知られています.
対象となるHTTPリクエストが送られる前後でセッションに紐づく値を引き回すことにより, ユーザーの意図的なアクションであることを確認できます.

OAuth 2.0では, OAuth Clientが現在のセッションに紐づくstateパラメータを用意し, 認可要求に含むことが推奨されています.
OAuth ServerはOAuth Clientに認可応答を返す際にそのstateパラメータを含み,OAuth Clientはその値とセッションの組み合わせを検証してからアクセストークン取得を試みます.

OAuth 2.0の認可フロー(stateパラメータあり)

この場合, ユーザーAがユーザーBに認可応答を渡してもstateパラメータがユーザーBのセッションに紐付いているものと異なります.
stateパラメータの検証に成功したもののみアクセストークンを取得するように実装することで, CSRF対策となります.

OAuth 2.0の認可フローにおけるCSRF(stateパラメータあり)

このように, stateパラメータの目的をOAuth Clientの開発者が理解し, 正しく実装することでCSRF対策が可能となります.
しかし, OAuth Serverは認可要求から”OAuth Clientがstateパラメータを利用しているかどうか”は知ることができるものの, “stateパラメータに共通の値や推測可能な値を指定していないか”, “OAuth Clientは正しく認可応答のstateパラメータの検証をしているか”までは知ることができません.

OAuth Clientが正しくstateパラメータのハンドリングを実装しているかどうかをOAuth Serverが個別に確認するのは手間もかかりますし, mixi Platformのように一般開発者向けにAPIを提供している場合は現実的ではありません.
そこで, 私たちはプラットフォーム全体の安全性がOAuth Clientの実装に大きく依存することを避けるため, OAuth ServerでCSRF対策を可能にする拡張仕様を検討しました.

拡張仕様

検討したのは, OAuth Serverがstateパラメータに相当するものを生成/検証してCSRF対策を行うものです.

OAuth 2.0の認可フロー(server_stateパラメータあり)

  1. OAuth Clientは認可要求を送る前に, OAuth Serverからserver_stateを取得. この時, OAuth Serverはserver_stateとOAuth Clientを紐付けて保存する
  2. OAuth Clientは取得したserver_stateをセッションに紐付けて保持しておき, 認可要求に含む
  3. OAuth ServerはOAuth Clientがアクセスを求めているユーザーデータについてユーザーに説明し, アクセス許可を得る
  4. OAuth Serverは認可応答に含まれる認可コードとserver_stateを紐付けて保存する
  5. OAuth Clientは認可コードを用いてアクセストークンを取得するとき, セッションに紐付くserver_stateを指定する OAuth Serverは認可コードとserver_stateの組み合わせを検証し, 一致した時のみアクセストークンを返す

ユーザーAがユーザーBに認可応答を渡しても, 手順5でOAuth Clientはアクセストークンを取得する際にユーザーBのセッションに紐付くserver_stateの値を指定します.
OAuth Serverが認可コードとserver_stateの組み合わせを検証するため, OAuth Clientの実装に依存せずにCSRF対策を実現できます.

OAuth 2.0の認可フローにおけるCSRF(server_stateパラメータあり)

以上のような拡張機能を, mixi Platformに導入しました.

mixi Platformへのリクエストとレスポンス

詳細は今後mixi Developer Centerに記載する予定ですが, 拡張仕様を利用するためのmixi Platformへのリクエストとレスポンスについて説明します.

OAuth Clientはserver_stateの値を取得するために, OAuth ServerのトークンエンドポイントにPOSTリクエストを送ります. 必要なパラメータは2つです.

  • grant_type : “server_state”という文字列を指定
  • client_id : mixi Developer Center内でConsumer keyと呼ばれているアプリケーションの識別子

リクエストのURLとリクエストボディは次のようになります.

  • URL
    https://secure.mixi-platform.com/2/token
  • リクエストボディ
    grant_type=server_state&client_id=908ed4da74f885a2ab

レスポンスは次の値を含みます.

  • server_state : server_stateの値
  • expired_in : server_stateの有効期限である秒数

OAuth Clientはこのserver_stateの値を認可エンドポイントに送るリクエストに含みます.

  • URL
    https://mixi.jp/connect_authorize.pl?
    client_id=908ed4da74f885a2ab&
    response_type=code&scope=r_profile%20r_voice&
    server_state=JO6PiL8Z9IdYYh2CMomHTsKEAJUYGdxw0ldrMEq32CU

ユーザーがリソースアクセスに同意した後, OAuth Serverから認可コード(codeパラメータ)が返されます. OAuth Clientはアクセストークン取得時にもserver_stateの値を含みます.

  • URL
    https://secure.mixi-platform.com/2/token
  • リクエストボディ(表示の都合で改行を入れています)
    grant_type=authorization_code
    &client_id=908ed4da74f885a2ab
    &client_secret=9720b4826e90ad9f053a57500d3a8c697c01d1
    &code=347ab1db9398d60b5ef3515e672d1e
    &redirect_uri=http%3A%2F%2Fserver.name%2Fredirect
    &server_state=JO6PiL8Z9IdYYh2CMomHTsKEAJUYGdxw0ldrMEq32CU

アクセストークン、リフレッシュトークンを含むレスポンスや取得した後の処理に変更はありません.

既存のmixi Platformを利用するアプリへの影響

現在, この拡張仕様の利用は任意であり, mixi Platformを利用する全てのアプリケーションに対して ○月○日までに対応をお願いするというようなものではございません.
既にmixi Platformを利用しているサービスを改修したり, 新たにmixi Platformを利用する際にはこの拡張仕様を利用してより安全な認可フローを実装していただければと思います.

今後の展開

今後は次のようなケースで新規にAPI提供を開始する際に, 本拡張機能の利用を必須化させることを検討しています.

  • OAuthのしくみを認証目的で利用するケース
  • お金が絡むなど, センシティブなデータを扱うケース

また, この拡張仕様を単なる"mixi独自拡張仕様"で終わらせず, 仕様策定の場であるIETFのOAuth WGへドラフトとして提案するなどしてコミュニティにも貢献できればと思っています.

ではまた!