Twitter の API 制限が増やせるとか

  • 投稿日:
  • by
  • カテゴリ:

Twitter の BASIC 認証が廃止されて、外部アプリ・サービスは OAuth 認証が必須になったわけですが、この OAuth 認証パラメーターの渡し方でおもしろいことに気付きました。

通常、OAuth 認証は次のようにアカウント認証に必要なパラメーターを HTTP リクエストの Authorization ヘッダで渡します。

GET /1/statuses/home_timeline.json?count=100 HTTP/1.1
Authorization: OAuth oauth_consumer_key="???",oauth_signature_method="HMAC-SHA1",oauth_timestamp="0000000000",oauth_nonce="???",oauth_version="1.0",oauth_token="00000000-????",oauth_signature="????"
Host: api.twitter.com
Accept-Encoding: gzip, deflate
User-Agent: boeboe
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive

これは BASIC 認証と同じ方法で、Authorization ヘッダにあるパラメーターでもってこのリクエストがどのアカウントを使ったものかを特定できるようになっています。ここに OAuth プロトコル的な正しいパラメーターを入れておかないと「アカウントが違います」的なエラーになる、という寸法ですね。

上の例ではセキュリティ的なアレを考慮して「正しいパラメーター」を全部消してますけど、ちゃんとやれば次のようなレスポンスが返ってきます。

HTTP/1.1 200 OK
Date: Tue, 07 Sep 2010 06:25:30 GMT
Server: hi
Status: 200 OK
X-Transaction: 0000000000-00000-00000
X-RateLimit-Limit: 350
ETag: "4f05766016563d89d76886504b8e9a39"-gzip
Last-Modified: Tue, 07 Sep 2010 06:25:30 GMT
X-RateLimit-Remaining: 349
X-Runtime: 0.13102
Content-Type: application/json; charset=utf-8
Pragma: no-cache
X-RateLimit-Class: api_identified
X-Revision: DEV
X-RateLimit-Reset: 1283844330
Set-Cookie: k=???; path=/; expires=Tue, 14-Sep-10 06:25:30 GMT; domain=.twitter.com
Set-Cookie: guest_id=0000; path=/; expires=Thu, 07 Oct 2010 06:25:30 GMT
Set-Cookie: lang=en; path=/
Set-Cookie: _twitter_sess=????; domain=.twitter.com; path=/
Vary: Accept-Encoding
Content-Encoding: gzip
Connection: close

例によってクッキーとかセッション ID とか全部消してますけど、このレスポンスにある X-RateLimit-Limit ヘッダに「最大 API 呼び出し回数」が、X-RateLimit-Remaining ヘッダに「残り回数」が入ってます。上の例だと 349 / 350 回、所定の API が呼び出せまっせ、ということになります。
で X-RateLimit-Reset ヘッダにある時間(値は 1970/1/1 0:00:00 からの経過秒数)に X-RateLimit-Remaining がリセットされますよ、ということもわかります。

ところが、Twitter だと OAuth 認証に必要な「正しいパラメーター」を違う渡し方をしても通ってしまうようなんですね。

その渡し方とは、次のように「正しいパラメーター」を GET の QUERY_STRING に渡してしまうというもの。

GET /1/statuses/home_timeline.json?count=100&oauth_consumer_key=????&oauth_nonce=????&oauth_signature_method=HMAC-SHA1&oauth_timestamp=0000000000&oauth_token=00000000-??&oauth_verifier=0000000&oauth_version=1.0&oauth_signature=???? HTTP/1.1
User-Agent: boeboe
Content-Type: application/x-www-form-urlencoded
Host: api.twitter.com
Connection: keep-alive

よく見ると oauth_verifier という上の例には付いてないパラメーターが付いてますけど、これは OAuth 認証手順の最初のほうにある「アクセストークンの取得」に必要なものですね。つまり API に渡すパラメーターにプラスして OAuth 認証のアクセストークン取得パラメーターを付けてるわけです。
これは POST のリクエストボディを使っても同様のようです。上の例では GET 使ってるので Content-Type ヘッダは不要ですけど、コピペしたら付いてきたのでそのままにしてるだけです。

こんな「間違った」やり方でも普通に自分のアカウントとして認証されて、レスポンスも全く問題なく得られるんですけど、その内容が少し違ってます。

HTTP/1.1 200 OK
Date: Tue, 07 Sep 2010 06:24:33 GMT
Server: hi
Status: 200 OK
X-Transaction: 0000000000-00000-0000
X-RateLimit-Limit: 150
ETag: "c532a31f7aecfca1c532c63ad654275d"
Last-Modified: Tue, 07 Sep 2010 06:24:31 GMT
X-RateLimit-Remaining: 149
X-Runtime: 1.42691
Content-Type: application/json; charset=utf-8
Pragma: no-cache
X-RateLimit-Class: api
X-Revision: DEV
X-RateLimit-Reset: 1283844271
Set-Cookie: k=????; path=/; expires=Tue, 14-Sep-10 06:24:31 GMT; domain=.twitter.com
Set-Cookie: guest_id=????; path=/; expires=Thu, 07 Oct 2010 06:24:31 GMT
Set-Cookie: lang=en; path=/
Set-Cookie: _twitter_sess=????; domain=.twitter.com; path=/
Vary: Accept-Encoding
Connection: close

このレスポンスだと X-RateLimit-Limit ヘッダが 150 に、X-RateLimit-Remaining ヘッダが 149 になってます。つまりあと 149 回所定の API 呼び出しができるってことで、最初の例と違う値になってるんですね。

タイムスタンプはいじってないので、どちらも続けて送ったリクエストです(実はふたつめの例のほうが先に実行されてるのもバレバレ)。
両者の数字の減り方は独立してて一方が 349/350 でもう一方が 149/150 だから、この 2 種類を使い分ければ 498/500 の API 呼び出しが可能になってしまう、と。

Twitter の OAuth 認証は API 呼び出し回数にアカウント毎の制限をはかけるのも目的のひとつっぽいですが、本来 350 回のところプラス 150 回余計に TL 取得したりユーザー情報見たりできるわけですよ奥さん!

これは普通に exploit だろ...常識的に考えて...。

なのでこれを利用して何かしら不利益を被ってもわたしは知りませんから試したければ自己責任でどうぞだし、Twitter に報告したいけど英語でこれを説明するの骨が折れるのでまだやってないわけだし、周辺調査とかもしてないから既知の現象かどうかも知らないだしし!

あと関係ないけど API コールでも cookie 喰わせようとしてるってことは、クライアント的には cookie 食べた振りしたほうがサーバーに対して優しい振る舞いになったりするんですかね。