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

в 9:30, , рубрики: android, mail.ru, Вконтакте, информационная безопасность, реверс-инжиниринг

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1. Загрузить и распаковать dex2jar и jd-gui
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, с командой:

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.

После этого, предварительно удалив оригинальное приложение, можно установить и запустить наш измененный клиент ВКонтакте. Для получения logcat с устройства воспользуемся Android Debug Bridge. Подключаем 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. Но потом все же отложил эту затею.

Я пытался обойти лимиты всеми возможными способами. Менял 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$. Данная уязвимость позволяла взломать большинство аккаунтов в социальной сети, в безопасности были только аккаунты с двухфакторной авторизацией (у них нельзя делать восстановление по номеру телефона). Репорт.

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

Автор: norver

Источник

Поделиться

* - обязательные к заполнению поля