- PVSM.RU - https://www.pvsm.ru -

Уязвимость ВКонтакте: отправляем сообщение с кодом восстановления страницы на чужой номер

Уязвимость ВКонтакте: отправляем сообщение с кодом восстановления страницы на чужой номер - 1

Обычным весенним днем, занимаясь «подготовкой» к ЕГЭ по информатике, наткнулся на статью об уязвимости Facebook [1], позволявшей взломать все аккаунты в социальной сети, за которую выплатили 15000$. Суть уязвимости заключалась в переборе кодов восстановления на тестовом домене компании. Я подумал, а чем собственно ВКонтакте хуже? И решил попробовать провернуть подобный трюк у них. Зная, что веб-версия уже достаточно хорошо исследована, жертвой должен был стать Android клиент, а что из этого вышло можно прочитать под катом.

Смотрим трафик

Первым делом я захотел узнать, какую информацию приложение передает в сеть во время процесса восстановления страницы. Помощником в этом деле выступил Fiddler, я настроил его и Android устройство, как написано в официальной документации [2]. Таким образом в Fiddler становятся доступны все HTTP/HTTPS запросы c устройства. Теперь, в приложении, смело выходим из аккаунта ВКонтакте и нажимаем на кнопку «Забыли пароль?». После ввода номера телефона приложение отправляет 2 HTTPS запроса. Особую ценность представляет второй, потому что именно он отвечает за отправку SMS с кодом восстановления.

Уязвимость ВКонтакте: отправляем сообщение с кодом восстановления страницы на чужой номер - 2

Особое внимание стоит обратить на некоторые параметры запроса:

phone — номер на который отправляется SMS
session_id — рандомно генерирующаяся сессия операции восстановления

Попытка отправить запрос изменив его не увенчалась успехом. Мешает параметр «signature», который выступает в роли «подписи», как она генерируется разберемся немного позже.

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

Уязвимость ВКонтакте: отправляем сообщение с кодом восстановления страницы на чужой номер - 3

Честно говоря на этом моменте мне хотелось начать перебирать коды восстановления, меняя значение параметра «code». К сожалению, и этот запрос защищен от изменения с помощью «signature». Придётся разобраться, как генерируется эта подпись.

Реверс инжиниринг: декомпиляция

Для первоначального анализа можно попробовать декомпилировать приложение ВКонтакте. Так можно получить некоторые части исходного кода на Java.

Как это сделать

1. Загрузить и распаковать dex2jar [3] и jd-gui [4]
2. Открыть apk приложения, как обычный архив, и «перетащить» .dex файлы на d2j-dex2jar.bat

Открываем в jd-gui все полученные .jar файлы. И не долго думая, делаем поиск по строке «signature».

Уязвимость ВКонтакте: отправляем сообщение с кодом восстановления страницы на чужой номер - 4

Библиотека libverify за авторством Mail.Ru явно выпадает из общего списка найденного. Смотрим и не ошибаемся, формируемая строка очень похожа на url из предыдущих запросов.

localObject3 = String.format(Locale.US, "%s%s?%s&signature=%s", new Object[] { d(), e(), localObject3, URLEncoder.encode(ru.mail.libverify.utils.m.b(f() + (String)localObject4 + ru.mail.libverify.utils.m.c(a.b())), "UTF-8") });

Эта библиотека сделана в лучших традициях security through obscurity, весь код надежно обфусцирован. Поэтому, через jd-gui мне удалось узнать только то, что за «signature» прячется MD5-хэш от неизвестной строки.

Уязвимость ВКонтакте: отправляем сообщение с кодом восстановления страницы на чужой номер - 5

Реверс инжиниринг: дизассемблирование

Мне требовалось узнать, что за строка поступает в функцию ru.mail.libverify.utils.m.b(). Самый простой способ сделать это — немного изменить код приложения. Ну что ж попробуем. Для начала используем apktool [5], с командой:

apktool.jar d vk.apk -r
(ключ -r для игнорирования ресурсов)

Теперь, в папках с smali-кодом находим файл в котором происходит генерация MD5. В моем случае путь был такой: smali_classes3rumaillibverifyutilsm.smali. Переходим к нужному методу:

...
.method public static b(Ljava/lang/String;)Ljava/lang/String;
    .locals 8
    .param p0    # Ljava/lang/String;
        .annotation build Landroid/support/annotation/NonNull;
        .end annotation
    .end param

    :try_start_0
    const-string/jumbo v0, "UTF-8"

    invoke-virtual {p0, v0}, Ljava/lang/String;->getBytes(Ljava/lang/String;)[B
    :try_end_0
    .catch Ljava/io/UnsupportedEncodingException; {:try_start_0 .. :try_end_0} :catch_2

    move-result-object v0

    :try_start_1
    const-string/jumbo v1, "MD5"

    invoke-static {v1}, Ljava/security/MessageDigest;->getInstance(Ljava/lang/String;)Ljava/security/MessageDigest;

    move-result-object v1

    invoke-virtual {v1}, Ljava/security/MessageDigest;->reset()V

    invoke-virtual {v1, v0}, Ljava/security/MessageDigest;->update([B)V

    invoke-virtual {v1}, Ljava/security/MessageDigest;->digest()[B

    move-result-object v0
    ...

Строка, которую требовалось узнать передавалась в функцию в первом параметр-регистре (p0). Поэтому, чтобы получить ее, следует куда-нибудь вывести параметр, например, в Logcat. Добавляем в код несколько строк:

...
.method public static b(Ljava/lang/String;)Ljava/lang/String;
    .locals 8
    .param p0    # Ljava/lang/String;
        .annotation build Landroid/support/annotation/NonNull;
        .end annotation
    .end param

    # PATCH
    # String v0 = "vk-research";
    const-string/jumbo v0, "vk-research"
    # Log.d(v0, p0), где p0 параметр метода
    invoke-static {v0, p0}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I

    :try_start_0
    const-string/jumbo v0, "UTF-8"
    ...

После сохранения изменений собираем приложение с помощью apktool:

apktool.jar b vk -o newvk.apk

Теперь нужно подписать apk, я использовалAPK Signer [6].

После этого, предварительно удалив оригинальное приложение, можно установить и запустить наш измененный клиент ВКонтакте. Для получения logcat с устройства воспользуемся Android Debug Bridge [7]. Подключаем Android-устройство по USB и последовательно выполняем команды:

adb devices
adb logcat

Как только присоединились к устройству и получили возможность смотреть логи, снова нажимаем на «Забыли пароль?» и вводим номер телефона. В окне adb появляется запись:

Уязвимость ВКонтакте: отправляем сообщение с кодом восстановления страницы на чужой номер - 6

Становится понятно, что хэшируемая строка состоит из последовательно склеенных: части url, параметров запроса и еще одной строки-хэша (506e786f377863526a7558536c644968). И теперь, зная алгоритм генерации «signature» можем начать отправлять свои «подписанные» запросы.

Исследование

Для исследования я написал простую программу на C#, которая отправляла запрос на отправку SMS и делала попытки ввода кода. Воспользовавшись ей, я вводил случайные коды восстановления. Но ожидаемо уперся в лимит попыток:

Уязвимость ВКонтакте: отправляем сообщение с кодом восстановления страницы на чужой номер - 7

Пытался повторно отправить смс, в надежде, что после этого пропадет лимит. К моему сожалению, сообщение приходило, но вводить код я все так же не мог:

Уязвимость ВКонтакте: отправляем сообщение с кодом восстановления страницы на чужой номер - 8

Решил отправлять запросы с разными session_id, приходили смс с другими кодами восстановления, но я все еще упирался в лимит, теперь уже не в «ATTEMPTLIMIT», а в «RATELIMIT».

Уязвимость ВКонтакте: отправляем сообщение с кодом восстановления страницы на чужой номер - 9

Про bruteforce

Наблюдая за приходящими SMS я заметил некоторую общую особенность кодов. Код восстановления состоял из 4 цифр (на момент написание статьи увеличили да 6) и одинаковые цифры не находились рядом. То есть всего ~6500 возможных вариантов кодов. Я подумал, что вполне возможно за 5 попыток угадать код, так например делали в Facebook [8]. Но потом все же отложил эту затею.

Я пытался обойти лимиты всеми возможными способами. Менял IP-адреса, параметры запроса, номера телефонов, но сделать больше 5 попыток ввода кода у меня не получалось.

И тут, почти случайно, я решил отправить код восстановления на два разных номера, но используя один и тот же session_id. Моему удивлению не было придела, когда я увидел одинаковую SMS на обоих телефонах. Вот как это работало:

Из-за прокси нужна дополнительная проверка.

Таким образом получалась атака:

  1. Отправляем запрос на отправку SMS абоненту A с session_id C, ему приходит код 1234
  2. Отправляем запрос на отправку SMS абоненту B с session_id C, ему приходит код 1234
  3. Теперь, если абонент A знает номер телефона абонента B, он может восстановить его страницу. Восстанавливать можно было только ту страницу, на номер которой пришла последняя SMS с сходным session_id.

Вывод

Сразу после обнаружения уязвимости я написал репорт на HackerOne. Уже через 17 часов уязвимость была устранена. Спустя несколько дней мне выплатили 2000$. Данная уязвимость позволяла взломать большинство аккаунтов в социальной сети, в безопасности были только аккаунты с двухфакторной авторизацией (у них нельзя делать восстановление по номеру телефона). Репорт [9].

P.S. ЕГЭ по информатике сдал на 97 баллов

Автор: norver

Источник [10]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/vkontakte/259957

Ссылки в тексте:

[1] статью об уязвимости Facebook: http://www.anandpraka.sh/2016/03/how-i-could-have-hacked-your-facebook.html

[2] официальной документации: http://docs.telerik.com/fiddler/Configure-Fiddler/Tasks/ConfigureForAndroid

[3] dex2jar: https://github.com/pxb1988/dex2jar/releases

[4] jd-gui: https://github.com/java-decompiler/jd-gui/releases

[5] apktool: https://ibotpeaches.github.io/Apktool/

[6] APK Signer: https://shatter-box.com/knowledgebase/android-apk-signing-tool-apk-signer/

[7] Android Debug Bridge: https://developer.android.com/studio/command-line/adb.html

[8] делали в Facebook: https://hackernoon.com/how-i-could-have-hacked-multiple-facebook-accounts-d9d335188d9b

[9] Репорт: https://hackerone.com/reports/219171

[10] Источник: https://habrahabr.ru/post/332684/?utm_source=habrahabr&utm_medium=rss&utm_campaign=sandbox