ParseっていうBaaSのソーシャル連携の実装がよろしくない気がする

おはようございます, ritouです.
これから寝ます.おやすみなさい.

※2012/06/03 : 本件について続きのエントリを書きましたので合わせてご覧ください ParseっていうBaaSのソーシャル連携の実装がよくなった気がする - r-weblife

ちょっと前にID厨としての師匠の一人がこんなこと書いてたのを思い出しました.

残念ながら、現在のBaaSのほとんどが、この「Facebookでログイン」「Twitterでログイン」を正しく実装できていません。具体的に言うと、トークンリプレースという攻撃によって他人になりすますことが簡単にできてしまいます。回避するワークアラウンドはあるのですが、対処されていません。そもそもすべてのBaaSベンダーがこの問題を正しく認識しているかどうか微妙です。

BaaS (Backend as a Service) について - snippets from shinichitomita’s journal

要は、FBのSDKとかでGETしたaccess tokenをそのままクライアントからサーバに投げて認証しちゃダメ、ってことなんですが、なんでだめなのかいまいちわかってもらえないケースが多いです。この記事はBaaSについてのものなので詳細は省きますが、興味があれば上記記事をじっくり読んでみてください。

BaaS (Backend as a Service) について - snippets from shinichitomita’s journal

これを見て何が問題かを気にしていただいた人もいるかもしれませんが、「まぁ後で」とスルーされた方もいると思います.
今回は, この記事が出てきたあたりにちょっと話題になっていたParseというBaaSの実装見たらOAuth 1.0a使ってるTwitterの方もよろしくないんではないかと思ったという話です.

今回気になったので調べたサービス

ソーシャル連携

これは各BaaSで異なると思うのですが, Parseの場合はこんなことができます.

  • Facebook/Twitterのアカウント(厳密には"認可"結果)で新規アカウント登録/ログイン
  • Facebook/Twitterのアカウントと他のアカウント(ID/PW)をLink

開発者向けドキュメントはこの辺です。https://www.parse.com/docs/rest#users-linking

図にするとこんな感じなのです.

試したこと

ParseをBackendとして利用するアプリをParseアプリと呼びます.

  • (1) TwitterにアプリケーションAを登録
  • (2) アプリケーションA経由のTwitterのアカウントを用いてParseアプリに新規アカウント登録する
  • (3) アプリケーションA経由のTwitterのアカウントを用いてParseアプリにログインする
  • (4) TwitterにアプリケーションBを登録
  • (5) アプリケーションB経由のTwitterのアカウントを用いてParseアプリに・・・

Twitter上のユーザーは普段から使ってる1つのアカウントとします.
TwitterのOAuth Clientが2つあって、それぞれを経由したアカウントでParseアプリの新規アカウント登録/ログインしようとするとどうなるかという話です.

試した結果

(1)は省略

(2) ですが、curlを用いて次のようなリクエストを送ります.

curl -X POST \
  -H "X-Parse-Application-Id: (Parseから発行されたApplication ID)" \
  -H "X-Parse-REST-API-Key: (Parseから発行された REST-API-Key)" \
  -H "Content-Type: application/json" \
  -d '{
        "authData": {
          "twitter": {
            "id": "14197251",
            "screen_name": "ritou",
            "consumer_key": "アプリケーションAのConsumer Key",
            "consumer_secret": "アプリケーションAのConsumer Secret",
            "auth_token": "アプリケーションAが受け取ったAccess Token",
            "auth_token_secret": "アプリケーションAが受け取ったAccess Token Secret"
          }
        }
      }' \
  https://api.parse.com/1/users

"なんと!Parseに向けてClientCredentialとTokenCredentialを全部丸投げ!これどうなのよ!"についてはちょっと置いといて, これでアプリケーションAに認可を与えたTwitterアカウントに対応するParseアプリ用ユーザーが作成されます.

{
  "sessionToken":"11...",
  "username":"bz...",
  "createdAt":"2012-05-xxTxx:xx:xx.xxxZ","objectId":"NO..."
}

Parseアプリは, ここで返されたsessionTokenをその名前の通りに使ってこのユーザーのデータをいろいろできるわけです.
(3)もう一度, 上記のようなリクエストを送ると, 同じusername, objectIdが返されます.

{
  "username":"bz...",
  "createdAt":"2012-05-xxTxx:xx:xx.xxxZ",
  "updatedAt":"2012-05-xxTxx:xx:xx.xxxZ",
  "objectId":"NO...",
  "sessionToken":"12...",
  "authData":{(略)}
}

これで, Twitterでログイン機能が実装できるわけです.
(4)次に別のOAuthアプリケーションBを用意します
(5)で、同じようなリクエストを送ります.

curl -X POST \
  -H "X-Parse-Application-Id: (Parseから発行されたApplication ID)" \
  -H "X-Parse-REST-API-Key: (Parseから発行された REST-API-Key)" \
  -H "Content-Type: application/json" \
  -d '{
        "authData": {
          "twitter": {
            "id": "14197251",
            "screen_name": "ritou",
            "consumer_key": "アプリケーションBのConsumer Key",
            "consumer_secret": "アプリケーションBのConsumer Secret",
            "auth_token": "アプリケーションBが受け取ったAccess Token",
            "auth_token_secret": "アプリケーションBが受け取ったAccess Token Secret"
          }
        }
      }' \
  https://api.parse.com/1/users

すると、(3)と同じようにログイン成功した的なレスポンスが返ってきます.

{
  "username":"bz...",
  "createdAt":"2012-05-xxTxx:xx:xx.xxxZ",
  "updatedAt":"2012-05-xxTxx:xx:xx.xxxZ",
  "objectId":"NO...",
  "sessionToken":"12...",
  "authData":{(略)}
}

検証はここまで.

わかったこと

  • 1つのParseアプリに対して, 複数のTwitterOAuthアプリケーションのアカウント(認可結果)を用いたアカウント登録/ログインできる
  • Twitter上で同一ユーザーであれば, 異なるTwiterOAuthアプリケーションからでもParseアプリ上の同じアカウントでログイン状態になる
  • ParseではあるParseアプリに対して利用できるTwitterOAuthアプリケーションを管理/制限して"いない"

私が問題と思ってること

現在の仕様では"悪意のあるアプリケーション開発者にParseアプリの認証機能を乗っ取られてしまうのではないか"ということです.

私が悪意を持っててMaciPhoneを持っててTwitterでログインできる有名なParseアプリを見つけたらこんなことをするでしょう

  • CharlesとかをProxyにしてParseアプリを使ってみて, X-Parse-Application-Id, X-Parse-REST-API-Keyの値を取得
  • 簡単なTwitter連携サービスを実装して, 被害者となるユーザーのAccess Token/Access Token Secretを集める
  • 超有名Parseアプリケーションになりすまして被害者となるユーザーのアカウントを作ったり既にあればログインしてキャッキャウフフ

あまり難しいことはありませんね.

HTTPSの中身見れるんだったらParseアプリがTwitterから発行されたConsumerKey/Secretまで取得可能な気もしますが, それすら使いません.

こうすればいいと思う

  • 各Parseアプリに対して利用できるTwitterのOAuth Consumerを制限する

そして, 以下の処理もParseがやればいいのではないかと.

  • Request Token/Secretの取得
  • Request Token/Secretとoauth_verifierからのAccess Tokenの取得

そうすれば悪意のある開発者もいじれるのはoauth_verifierぐらいだし, HTTPSとはいえアプリ-Parse間でSecretが送られる機会も減るしでまぁまぁ良いのではないかと思いました.

今回の件は, いちおう4月ぐらいにこれParseのどこかにメールしてみましたがスルーされました.
他のソーシャル連携してるBaaSも少しずつ見ていきましょうかね.

ではまた!

※2012/05/25 追記
Twitterで質問をいただきましたので残しておきます.
質問
「ユーザーが認可するのはParseアプリだと思うのですが、そのAccessTokenをParseプラットフォームに渡すのは認可先の観点で問題ないのでしょうか。」
私の見解
「現状もアクセストークンはParseプラットフォームに渡されるので、認可先の問題はあると思います。○○プラットフォーム上で動く▲▲アプリとして動くことをポリシーURLとともにユーザーに伝えて同意をもらうとかしないといけない気はしています。」
モバイルアプリというクレデンシャル/トークンの保管に限界があるようなところよりも, プラットフォーム側で安全に管理してくれるほうが良いと思っています.

他にもご意見・ご質問がありましたら @ritou までお願いします。


※2012/06/03 追記 本件について続きのエントリを書きましたので合わせてご覧ください ParseっていうBaaSのソーシャル連携の実装がよくなった気がする - r-weblife