単なる OAuth 2.0 を認証に使うと、車が通れるほどのどでかいセキュリティー・ホールができる

OAuth 2.0 の implicit grant flow を認証に使うと、車が通れる程どてかいセキュリティ・ホールが開くよ、と言う、ジョン・ブラッドレー氏[1]による良記事。コメントも読み応えあります。ちょっとチェックした見たところは、全滅。RP側を治さなきゃいけないから、とっとと公開アナウンスしたほうが良いのでしょうね。いちいちコンタクトしてられないし。
Facebook や、その他OAuthログインしているサイトはみんなチェク!

In some of the feedback I have gotten on the openID Connect spec, the statement is made that Connect is too complicated. That OAuth 2.0 is all you need to do authentication. Many point to Identity Pro…

英語読みたくないという人のために簡単に解説すると…

OAuth 2.0 の implicit flow を使って「認証」をしようとすると、とっても大きな穴が開きます。

カット&ペーストアタックが可能だからです。

OAuth 認証?は、図1のような流れになります。

図1 OAuth 認証?の流れ

一見、問題なさそうに見えます。しかし、それはすべてのサイトが「良いサイト」ならばです。

Site_A が実は悪いサイトだったとしましょう。すると、Site_A は、このユーザになり変わる access_token をまんまと入手してしまったことになります。

Site_A は、以後、このユーザになりすまして、任意の「OAuth 認証?」をやっているサイトにログインすることができます。

非技術者のためのOAuth認証(?)とOpenIDの違い入門 にも書きましたが、宛先の書いてない合鍵渡しちゃって、それを持っている人は誰でも私の分身ですと言っているわけですから当たり前ですね。

具体的に書くと、

Site_A はブラウザ(User Agent) UAを使って、Site_B に行ってログインしようとします。すると、上記と同じ手続で Site_B は「認証」をしようとします。UAは Site_B用の、攻撃者用のアクセストークン access_token_B をOAuth の Authorization Endpoint (Authz) からもらいますが、これをSite_Bには渡さずに、さっき取得した、ユーザ(=被害者)のアクセストークン access_token_A を代わりに渡します。Site_Bがこのトークンが本当はSite_A用のものだと認識する手段はありません。なので、自分向けのものとして受け取ってしまいます。そして、Site_Bは GraphAPI に access_token を投げて、被害者のemail や user_id を取得しようとします。GraphAPI が、このSite_A用のトークンを送ってきているのがSite_Bだということを認識する手段もありません。したがって、GraphAPIは、Site_Aがリクエストしてきたのと同様に、被害者のemailやuser_idを送り返してしまいます。結果、Site_Bは、攻撃者を被害者としてログインさせてしまいます[5]。この流れが図2です。

図2 OAuth の access_token 置換え攻撃

これは、OAuth の state パラメータを使って XSRF 対策をしていても防げません。つまり、OAuth 2.0 の Client は、そのClient (サイト)にログインしたすべての人になりすまして、任意の他のOAuth 対応サイトにログインできるのです。

これは、OAuth の問題ではありません。

OAuth は Authorization Delegation Protocol = 認可をデリゲーションするためのプロトコルであって、ユーザ認証のためのプロトコルではないからです[5]。はっきり言って、楽ちんだからといって、それを単体で認証の代わりに使っている方が悪い。

実は、Facebook もこのことは気づいていて、signed_request というAPIを持っています。これはほとんど OpenID Connect と同じです[2]。Facebook でログインするためには、こちらを使わなければいけないのです。scope=signed_request ってやるんですよ。でも、使っている人、どれくらい居ますか?やってます?ほとんどは、access token を取得するための client side flow (Facebook のデフォルト) を認証の代わりにつかっちゃってますよね?![7]

Google の Identity Service の責任者の Eric Sachs 氏の、John の blog に寄せられた投稿も、このことの重要性を指摘しています。

OpenID Connect ではなく、単なるOAuth を認証に使っているIdPが巨大なセキュリティホールを生み出しているということに関する、ジョン・ブラッドレー氏によるすばらしい記事。これは、至るところで繰り返し言い続けなければならない。IdPに対しては、パートナーに対してセキュリティ上の問題を生んでいるということを理解してもらうために。RPsには、数行のコードをケチったために、自らのセキュリティを台無しにしているということに気付いてもらうために。数年前、Googleが現在のOAuthにあたる独自API「AuthSub」を公開したときは、まさにこの理由のために「認証に使ってはいけない」旨を、ドキュメントの最後に大きく掲載していた。[3]

問題の原因は、access_token の audience は resource endpoint であるのに対して、認証に使うトークンの audience は client でなければいけないというところにあります。だから、OpenID Connect では、client を audience にした id_token という、access_token とは別のトークンを発行しているのです。Facebook の signed_request も同じです。

ちゃんと治してくださいね、皆さん。治すってことは、OpenID Connect 対応するってことですよ!

大した工数じゃないんだから。数行を惜しんでユーザを危険に晒す[4][8]のは、ぜひやめていただきたいところです。

 

[1] John Bradley. アメリカ政府の ICAMの中の人で、IMI, OpenID, SAML のプロファイルを書いている。OpenID Foundation 理事。Kantara Initiative リーダーシップカウンシル議長。今回の記事は、OAuth 認証のプロファイルを書こうとして、「だめだこりゃ」ということらしい。

[2] signed_request は、Facebook 独自の署名方式をとっているのに対して、OpenID Connect は IETF JOSE WG で標準化されているJWSを利用しています。また、signed_request では、access_token 自体をsigned_request の中に入れていますが、OpenID Connect では、他のOAuth 2.0 サイトとの互換性を考慮して、外出しにしています。

[3] 原文: Great post by John Bradley on the huge security hole many IDPs have created by using plain OAuth, instead of OpenIDConnect, for authentication. We need to keep hammering away on this point both so IDPs realize the security problems they are creating for their partners, and to get RPs to realize how easily they can compromise their own security just because of the lack of a few additional lines of code. Years ago when Google first launched its proprietary equivalent of OAuth, called AuthSub, we had a big section at the bottom warning people not to use it for authentication for exactly this reason. (source: https://plus.google.com/u/0/102425765611793764729/posts/UKcZQzuvosQ )

[4] 乗っ取られたとしても何も起きないのならば良いのですが、ユーザの個人情報を貯めてたりしたら、当然個人情報漏えい事件になりますよね。

[5] 太字部分、2/3追記。

[6] (2/3追記)攻撃者であるSite_Aが、自分あての access_token を他の人に渡すのは、Site_Aが自分でアクセスした結果を渡すのと得られる結果は同じです。したがって、GraphAPI/Resourceの提供者の立場からしたら、Site_A用の access_token を Site_Bが使うことは、別にリスクが増加していることにはなりません。

[7] (2/17追記)自前でコードを書くのではなく、Facebook の Javascript SDK を使う場合、signed_request を使うように成っている。さらに、昨年の7月からは、ドキュメントと違い、signed request には access_token ではなく code が入るように変わっていた。多くの人が JS SDK を使って、自前でコードを書いていないとすれば、FBに関しては被害はそれほどでもないということになる。ただし、iOS SDK や Android SDK はこのような対策が取られていないので、危ない。これは、 @nov が apple にインシデントレポートを上げている。

[8] (2/17追記) そもそも、Javascript などクライアントデバイス上のものに渡した access_token を、サーバサイドに渡すのはありえない。渡さなければ、たかだか攻撃者のデバイス上にある情報か、プラットフォーム側にあって、もともと攻撃者がアクセス認可をうけていたデータしか取れないはずだから問題は無いはずだという指摘もある。これはそうあって欲しいと願うのだが、たとえば Facebook の開発者向け資料 (https://developers.facebook.com/docs/authentication/) などでは、サーバに送るように図が書いてある。


@_Nat Zoneをもっと見る

購読すると最新の投稿がメールで送信されます。

「単なる OAuth 2.0 を認証に使うと、車が通れるほどのどでかいセキュリティー・ホールができる」への17件のフィードバック

  1. 図1はimplicit flowの通常のflowようですが、implicit flowではbrowserにoauth clientが存在するようなケースなので、もしsite-Aがサーバサイドを示すのであれば、site-Aにaccess_tokenを渡す図は少しcode flowと混乱してしまうかもしれません。そういう意味ではFB devのclient-side flowの’Your app’の書き方もややわかりにくい気がします。もちろんclient(site-Aのスクリプト)がサーバサイドにaccess_tokenをおくってしまうのは防ぎようがないですが。
    図2によるとトークン置き換え攻撃はUA側でやっているようですが、Site-Aの悪意あるスクリプトはSite-BへのHTTP redirect responseを捕捉可能なのでしょうか?ブラウザのセキュリティがないことが前提なのでしょうか?
    興味あるトピックなので質問させていただきました。全然ピント外れでしたらすいません。

    1. Siteってかかれると混乱するかもですね。図1では、code flowだろうがimplicit flowだろうが、攻撃者が運営するSite Aがaccess_tokenを取得できれば何でもいいです。なので、Site-AはiPhoneアプリかもしれないし、apps.facebook.com上のiframeアプリかもしれないし、NY TimesみたいにFB連携してる外部サイトかもしれないです。 図2では、UAを攻撃者が持ってるJailbreak済のiPhone、Site-BがZy○gaの新作iPhoneゲームだと思うと、考えやすいかもしれません。ここではSite-Aのスクリプトは特に必要ありません。

      1. ありがとうございます。implicitで払い出されたaccess tokenが例えば払い出し先のログインセッション情報(cookieとか)と結びつけられてAuthZ/Rscサーバがそれをもとにチェックできればまだ防げるのでしょうが、そんなにうまくはいかないんでしょうね。

        1. ご指摘のとおり、OP側ではアクセストークンとRP識別子などを紐付けて管理していると思いますし、セッション情報などを指定して引き回すstateパラメータも紐付け、確認できるようにすれば検証は可能ですね。しかし、アクセストークンはリソースアクセスのためのものですし、現行のOAuth 2.0のアクセストークンの仕様ではそうなっておりません。OpenID Connectで提案されたID Tokenはアクセストークンと別に認証イベントの情報取得のためのトークンとして定義されており、OAuth 2.0の仕様に影響を与えないように考慮しています。また、stateパラメータと同じような意味合いのnonceパラメータを含むことでリプレイアタックも防げるというような仕様となっています。

      2. SiteよりRPと書いたほうが良かったですね。今にして思うと。なんか、RPじゃわかんない人居るかなと思って、途中でClientに書きなおし、さらにそれをSiteに書きなおして失敗しました。
        シーケンスも、かきながら、後で直そうと思ってそのまま…。

  2. 勉強になりましたが。しかし、Oauthを認証に使っているサイトってそんなに沢山あるのでしょうか?Oauthを使って認証ということはつまり、サイト自身への認証を他のサービスプロバイダに任せてしまう、ということになるかと思いますが、このようなことが一般的に行われているのでしょうか?

    自分が正しく問題を理解できているのかやや不安なので、Oauthを認証に使っているサイトを例として教えて頂けると助かります。

    1. 山のようにありますよ。Facebook でログインとかしているところは、OAuth でのログインです。たとえば、このコメントシステムの disqus もそうですよね。サイト自身への認証を他のサービスプロバイダに任せることを認証連携とか、フェデレーションとか言います。技術的には OpenID や SAMLなどもこの仲間ですね。普通のサイトは、殆どの場合、認証をしっかりやることができない(まさか、ユーザ名パスワードだけでやろうなんて思ってないですよね?GoogleやFacebookなどは、パスワード認証に見えて、実は裏ではバリバリにリスクベース認証を走らせています。)ので、ちゃんとやっているところに任せたほうが世のため人のためです。

      逆にお聞きしますが、第三者に任せることの何が不安なんでしょうか?

  3. 素朴な疑問です。”scope=signed_request” はドキュメントされていないようです。
    https://developers.facebook.com/docs/authentication/permissions/

    > A signed_request parameter is used by Facebook to pass data to an application
    という技術だとすると、この文脈でどういう意味を持つのか分かりませんでした。
    https://developers.facebook.com/docs/authentication/signed_request/

    また、「client-side flowを認証に使うな」という記事の主旨に則って、一つ質問させて頂きます。

    Facebook iOS SDKのSSO (Single Sign-On)機能は、ネイティブFacebookアプリとのアプリ間通信によって(アプリでログイン済ならボタン一発で認可が完了する)スムーズな認可を行うとても便利な機能ですね。よく認証に使われています。”Single Sign-On” というくらいですから、Facebookも認証用途を想定していると思うのです。
    https://developers.facebook.com/docs/mobile/ios/build/#implementsso

    しかし、このSSO機能はcliend-side flowしか実装していないようです。これを認証に使うと、ご指摘のようなコピペ攻撃が可能になってしまうので、とても拙い設計だと思います。いかがでしょうか?

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください