ひだまりソケットは壊れない

ソフトウェア開発に関する話を書きます。 最近は主に Android アプリ、Windows アプリ (UWP アプリ)、Java 関係です。

まじめなことを書くつもりでやっています。 適当なことは 「一角獣は夜に啼く」 に書いています。

特定のクライアントを許可している Twitter ユーザーの Token Credentials を入手する攻撃

怪しいクライアントを許可していないのに勝手に twitter で DM が送信されていた

何やら Twitter で勝手に DM が送られるという事案が発生していた模様。 調査の結果、ある web ページにアクセスしただけで、Twitter の token credentials が攻撃者に知られてしまう *1 ということがわかったらしい。 下記ページにまとめられていたのだけどパッと読んですぐには攻撃手法を理解できなかったので、いろいろ考えたことを書き残しておく。

簡潔な解説が以下の記事にあったので、簡潔な説明で理解できる人は下記記事参照。

mala さんの記事も参照のこと。

また、過去にも mirai-iro さんがこの攻撃方法について指摘されていた。

OAuth 1.0 の仕組み

そもそもの OAuth 1.0 の仕組みから。 用語などは RFC 5849 をもとにしている。 OAuth の仕組みがわかっている人は読む必要なし。

登場人物

OAuth は、あるサーバー上にリソースを持っているユーザーが、第三者に対してリソースへのアクセス権限を与えるためのものである。 登場人物は以下。

  • サーバー (server: Twitter)
  • クライアント (client: Twitter クライアントや Twitter 連携する web サービスなど)
  • リソース所有者 (resource owner: Twitter ユーザーであり、クライアントに対して Twitter 上のリソースアクセスを許可しようと思っている人)

Credentials

Credentials というのは、クライアントまたはリソース所有者を一意に決定するための識別子とそれに伴う共有鍵の組。 OAuth では 3 種類の credentials が使用される。

  • Client Credentials : サーバーからクライアントに対して発行されるもの; クライアントを一意に決定する (古い仕様だと Consumer Key, Consumer Secret)
  • Temporary Credentials : クライアントが Token Credentials を取得する際の処理中に使われる仮の credentials (古い仕様だと Request Token, Request Secret)
  • Token Credentials : リソース所有者を特定するためのもので、これを使ってクライアントはサーバー上のリソースにアクセスできる (古い仕様だと Access Token, Access Secret)

OAuth による認可の流れ (Token Credentials の取得まで)

クライアントによる Temporary Credentials の取得

OAuth による認可の流れは、通常、リソース所有者が 「クライアントにサーバーへのアクセス許可を与えよう」 と思ったところから始まる。 例えば以下のようなユーザー操作が引き金となる。

  • Twitter クライアントで 『ログインする』 とか 『認証開始』 みたいなボタンをクリックする
  • Web サービスで 『Twitter 連携する』 みたいなボタンをクリックする

リソース所有者による上記のような操作を引き金として、クライアントは temporary credentials をサーバーから取得する。 このとき、リクエストを認証するためにクライアントは client credentials のみを使用する。

リソース所有者によるクライアントの許可

サーバーは、リソース所有者がクライアントを許可するかどうかを確認するためのエンドポイントを提供している。 Temporary credentials を取得した後、クライアントはそのページにリソース所有者を誘導する。 その際に、クライアントは URL のクエリパラメータとして temporary credentials の token (識別子) を与える必要があり、サーバーはそれを使用してリソース所有者と temporary credentials の関連付けを行う。

Twitter だと以下のようなページである。

f:id:nobuoka:20130301014553p:plain

リソース所有者がクライアントを許可すると、クライアントがリダイレクト先 URL を指定していた場合は、その URL にリダイレクトされる。 そのとき、URL のクエリパラメータに verifier と temporary credentials の token が与えられる。

クライアントがリダイレクトを指定していない場合は、サーバーのページ上に verifier が表示される。

クライアントによる Token Credentials の取得

リソース所有者によるクライアント承認の後リダイレクトが行われた場合は、クライアントはリダイレクト先 URL から verifier を得て、temporary credentials を特定することができる。 これらの情報を使ってサーバーにリクエストを行うことで、クライアントはサーバーから token credentials を取得できる。

リソース所有者による許可の後にリダイレクトされず、サーバー上に verifier が表示された場合は、クライアントはリソース所有者に verifier を手入力してもらって、そのあとは同じようにして token credentials を得る。

そのあとは、token credentials を利用することでクライアントはサーバー上のリソースにアクセス可能となる。

今回の攻撃の話

今回の問題は、ユーザーが過去に許可したクライアントのふりをした偽のクライアントが、ユーザーの明示的な許可行動 *2 を伴うことなく token credentials を取得できる、というものである。

攻撃手法 (攻撃者はいかにしてユーザーの明示的な許可行動なしに token credentials を取得したのか)

攻撃者による Temporary token の取得

攻撃者は、いくつかの有名な twitter クライアントの client credentials を知っていた。 それらを使うと、本物のふりをして temporary credentials の取得を行うことができる *3。 攻撃者は、リクエストを受け取ると自動的に偽のクライアントで temporary token を取得するようなサーバーを立てていたと考えられる。

ユーザーによるクライアントの許可がなくても verifier を入手可能

OAuth において、攻撃者が token credentials を入手するための最大の関門がここであるはずである。 本来であれば、ユーザーが 「許可する」 ボタンを押さなければ、クライアントは token credentials を取得できない。

攻撃者が提供する web ページ上に透明な iframe を配置し、その中に OAuth によるクライアント許可の確認ページを表示し、ユーザーのクリック操作により (ユーザーに気付かせることなく) 「許可する」 ボタンを押させる、というような攻撃手法 (クリックジャッキング) も考えられるが、Twitter のクライアント許可確認ページは X-Frame-Options がついていて、そもそも iframe 内に表示できない。

では、攻撃者がどのような手法をとったのかというと、本来の OAuth による認可の流れではなく、Twitter が独自に提供している sign in with twitter という機能を使用するというものである。

Sign in with twitter というのは、OAuth の 「リソース所有者によるクライアントへの許可」 の部分を twitter 独自のものに置き換えたもので、過去にリソース所有者がそのクライアントを許可していた場合は、許可確認画面を表示することなくリダイレクトする というものである。

なので、有名な twitter クライアントのふりをした偽クライアントで sign in with twitter の処理に入ると、攻撃対象者がそのクライアントを使用していれば自動的に指定のリダイレクト先にリダイレクトされてしまう。 リダイレクト先を攻撃者が保有するサーバーにしておけば、攻撃者は verifier を入手することができる。

攻撃者による Token Credentials の取得

Temporary credentials も攻撃者が取得したものであり、さらに verifier も取得してしまえば、攻撃者は token credentials を取得することができる。 その token credentials を使用すれば、サーバー上にあるユーザーのリソースに攻撃者はアクセス可能となる。

論点

Client credentials が攻撃者に知られていたということ

そもそも client credentials を攻撃者に知られていなかったならば、この攻撃手法は可能でない。

では OAuth のクライアント側は client credentials が絶対に漏れないように注意すべきなのか、漏れた場合はクライアント側の責任であるのか、ということになるが、実際のところ client credentials が漏れないようにするのは困難である。 実際、RFC 5849 にも以下のように書かれている。

4.6. Secrecy of the Client Credentials

In many cases, the client application will be under the control of potentially untrusted parties. For example, if the client is a desktop application with freely available source code or an executable binary, an attacker may be able to download a copy for analysis. In such cases, attackers will be able to recover the client credentials.

Accordingly, servers should not use the client credentials alone to verify the identity of the client. Where possible, other factors such as IP address should be used as well.

RFC 5849 — section 4.6

Client credentials は、他人に知られてしまう可能性があり、それを前提にセキュリティを考えなければならない。

ダイレクト先 URI を自由に設定できるということ

リソース所有者によるクライアント許可の後のリダイレクト先は、temporary credentials を取得のする際のリクエストに含めて指定するようになっている。 なので、攻撃者が temporary credentials の取得リクエストを自由に発行できる場合は、リダイレクト先 URI も指定できてしまう。

ダイレクト先 URI として攻撃者のサーバーを指定できなければ今回の攻撃は不可能なので、リダイレクト先を temporary credentials 取得の際に指定するという仕様は微妙なのかもしれない。 RFC 5849 には以下のように書かれている (by other means) ので、サーバー側の実装として、temporary credentials 取得の際のリクエストに含まれる "oauth_callback" の指定は無視して、別の手法で指定されたリダイレクト先にリダイレクトするようにしておくというのが少し安全な実装な気がする。

After receiving an authorization decision from the resource owner, the server redirects the resource owner to the callback URI if one was provided in the "oauth_callback" parameter or by other means.

ちなみに Twitter では、クライアントの設定画面に callback URI を入力する欄があるが、その欄への入力よりも temporary credentials 取得時の指定が優先される。 Temporary credentials 取得時に callback URI の指定がない場合にだけ、そこでの入力が使われるようである。

一方で、そこを空にしているとリダイレクト先として "oob" (リダイレクトなし) しか指定できず、"oob" 以外を temporary credentials 取得の際に指定すると以下のような本文を持つ 401 レスポンスが返ってきた。

<?xml version="1.0" encoding="UTF-8"?>
<hash>
  <request>/oauth/request_token</request>
  <error>Desktop applications only support the oauth_callback value 'oob'</error>
</hash>

(ここら辺の Twitter まわりの検証は 2013-03-01 03:00 JST に行ったものであり、そのうち変更される可能性はある。)

Sign in with twitter という仕組みがあること

本来の OAuth の仕組みだと、たとえ偽のクライアントが temporary credentials を取得したとしても、ユーザーが明示的に 「許可する」 ボタンを押さなければクライアントは許可されない。 しかし、Sign in with twitter という仕組みでは、過去に許可したクライアントに対しては、リソース所有者の明示的な許可行動なしに verifier が渡される。

この仕組みさえなければ今回の攻撃はできなかったはずだし、OAuth 仕様にない独自実装なわけなので、この存在が今回の攻撃の一番のキモという感じがする。

X-Frame-Options とリダイレクト

下記記事を見ていて知ったのだけど、X-Frame-Options で iframe 内への表示を禁止していても、リダイレクトはされるっぽい。

あんまりちゃんと調べてないけど HTTP 的にはどういう仕様なんだろう。 リダイレクト優先なんだろうか。

ソフトウェア開発者的感想

上でも書いたように Sign in with twitter という仕組みの存在が今回の攻撃のキモだった気がするのだけど、そういう仕様の一部を改変した独自実装を行うときには細心の注意を払う必要があるよなー、と思った。 (もちろん仕様にしたがってればセキュアだとは限らないけれど。)

*1:すべてのユーザーのものではない

*2:具体的には、クライアントを許可する、というボタンをクリックすること

*3:おそらく client credentials 以外でのクライアント識別は行っていないため。