Skip to content

Latest commit

 

History

History
369 lines (269 loc) · 21.2 KB

HOWITWORKS.md

File metadata and controls

369 lines (269 loc) · 21.2 KB

Реверс-инженерим API АСУ РСО

Таблица контента

Здесь описаны мои заметки о том, как работает их апи. Очень надеюсь, команда разработчиков не увидит эту библиотеку и не поменяет ничего :)

⚠️ ВАЖНО! Это не документация пакета, если вы просто хотите использовать библиотеку, перейдите на страницу README.md⚠️

Вход в аккаунт

Авторизация всех запросов базируется всего на двух ключах: куки ESRNSec и заголовок at. Оба получаются во время авторизации с помощью логина и пароля. Лезть в госуслуги я не хочу и не буду, может после того как уеду из России попробую описать их апи.

Для авторизации нужно:

  1. Сделать POST запрос на https://asurso.ru/webapi/auth/getdata

Мы получим ответ в JSON и куки NSSESSIONID

{
  "lt": "число 1*",
  "ver": "число 2*",
  "salt": "число 3*"
}
  1. Сделать POST запрос на https://asurso.ru/webapi/login с куки NSSESSIONID и телом в x-www-form-urlencoded в следующем формате:
  LoginType: 1
  cid: [id страны, всегда 2]
  sid: [id региона]
  pid: [id округа/района]
  cn: [id города]
  sft: [id ТИПА образовательной организации]
  scid: [id образовательной организации]
  UN: [логин, строка]
  PW: [первые n символов хеша пароля, где n - длина самого пароля]
  lt: [число 1*]
  pw2: [полный хеш пароля]
  ver: [число 2*]

Все ID вы можете найти в файле LOGINIDS.md

Чтобы получить значения полей PW и pw2 необходимо взять ваш пароль, хешировать его с MD5, а затем вначале конкатенировать к нему полученную в 1 шаге соль (число 3*) и снова хешировать. Таким образом полностью алгоритм будет выглядеть так:

pw2 = md5((число 3*) + md5(password))

Зачем хешировать пароль на фронтенде да еще и с MD5 лучше спросите разработчика tls и асурсо)

  1. В ответ мы получим JSON следующего вида:
{
  "at": "[число at]",
  "code": null,
  "timeOut": 3600000,
  "accessToken": "...",
  "refreshToken": "...",
  "accountInfo": {
    "activeToken": null,
    "secureActiveToken": "...",
    "currentOrganization": {
      "id": ...,
      "name": "..."
    },
    "user": {
      "id": ...,
      "name": "..."
    },
    "userRoles": [],
    "organizations": [
      {
        "id": ...,
        "name": "..."
      },
      {
        "id": ...,
        "name": "..."
      }
    ],
    "loginTime": "2022-01-13T22:37:38.7883797",
    "active": true,
    "canLogin": true,
    "storeTokens": true,
    "accessToken": "..."
  },
  "tokenType": "Bearer",
  "entryPoint": "/asp/SecurityWarning.asp",
  "requestData": {
    "warnType": "1"
  },
  "errorMessage": null
}

Отсюда нам нужно лишь число at, его очень важно сохранить, ведь без него вы не сможете делать ни один запрос. Более того, оно используется в браузере, ведь когда вы входите в систему это число будет постоянно с вами, а при переходе вы делаете не GET а POST запрос. Мне такая логика кажется очень странной, ведь если обновить страницу, число потеряется из-за удаления сессии в браузере, но не на бекенде.

Короче, забирайте "at" и идем дальше.

Куки ESRNSec ставится самим запросом (заголовок set-cookie). Самостоятельно генерировать ничего не нужно, просто сохраните значение.

Запросы к серверу

Все запросы, кроме тех, что для входа, должны содержать в себе заголовок at с ключом. Для входа также не забудьте добавить referrer: https://asurso.ru/about.html

Вам понадобится получить studentID, в браузере он получается при GET запросе на https://asurso.ru/webapi/student/diary/init В массиве students берем первый и единственный объект и там уже находим поле studentId.

Если вы походите по сайту то увидите что иногда делаются запросы html темплейтов, например pastmandatory.component и diaryday.component, не знаю зачем это нужно. Это что-то на ангулярском 😬

ID разных вещей

В основном я собирал их через значения внутри select, но некоторые пришлось парсить.

Почта

Получение почты происходит отправкой GET запроса на https://asurso.ru/asp/ajax/GetMessagesAjax.asp с query-параметрами AT, nBoxID, jtStartIndex, jtPageSize

Для отправки используется форма и POST-запрос на https://asurso.ru/asp/Messages/sendsavemsg.asp с content-type: application/x-www-form-urlencoded со следующими параметрами:

LoginType: 0
AT: вашТокенAt
AntiForgeryToken: xsrfТокен
MID:
MBID: 3
LTO: IDполучателя
LCC:
LBC:
TA:
NA:
PP:
DMID:
RT:
DESTINATION:
ShortAttach: 1
EDITUSERID: IDотправителя
ACC:
ABC:
SU: темаПисьма
BO: телоПисьма
STMSGREPORT:

VER, ATO, ACC, ABC, NEEDNOTIFY необязательные

LoginType, MBID, ShortAttach это константы

AntiForgeryToken можно получить только GET запросом на https://asurso.ru/asp/Messages/composemessage.asp?at=[вашAtТокен] и парсингом HTML кода страницы (css-селектор: form > [name=AntiForgeryToken])

Также нужно вместе с запросом на отправку почты отправить куки с ключом AntiForgeryToken, полученное из GET-запроса на composemessage.asp

LTO — ID получателя

LCC — ID получателя копии

LBC — ID получателя скрытой копии

ATO, ACC, ABC — три поля выше, но в виде текста (не обрабатываются бекендом)

NEEDNOTIFY — необязательно, если установлено значение 1, будет получено уведомление о прочтении

MID, TA, NA, PP, DMID, RT, DESTINATION, STMSGREPORT — неизвестно, что делают эти поля, они пустые

attachment — необязательно, ID вложенного файла

Новости (объявления)

Для получения новостей используется метод GET на https://asurso.ru/webapi/announcements с query-параметрами take и fullVersion, первый указывает кол-во получаемых новостей в массиве, а второй неизвестно что. В ответ получаем JSON массив с новостями. Если к новости прикреплен файл, он будет в массиве объектов attachments, где почему-то есть название исходного файла originalFileName.

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

Чтобы скачать файл надо сделать запрос на https://asurso.ru/webapi/attachments/[fileID] и в ответ получим сам файл с соответствующим заголовком content-type, например application/msword. Также в ответе будут заголовки filename с url-encoded названием файла, expires равный -1, content-disposition и content-length.

Портфолио

В портфолио можно создавать свои собственные разделы и редактировать те, что добавлены по умолчанию.

image

Запрос генерации файлов и работа в real-time

Как мы уже поняли, асурсо работает на asp, asp работает на c#, c# сделан microsoft, microsoft управляется биллом гейтсом, а он в свою очередь рептилоид, поэтому сделал SignalR чтобы высасывать из людей душу и жизненнные силы пока они с ним разбираются.

В АСУ РСО для генерации файлов, например Отчет об успеваемости, необходимо запросить его через веб-сокет. Чтобы сократить размер пакета на 1.18 МБ (а именно столько весит официальный клиент signalr), я решил работать напрямую.

  1. Сделаем GET-запрос на https://asurso.ru/WebApi/signalr/negotiate c параметрами

clientProtocol=1.5 at=вашЗаголовокAt connectionData=[{"name":"queuehub"}]

Не забудьте все это закодировать в URL percent encoding

Также в куки обязятельно указываем ESRNSec токен. Заголовок at не нужен.

В ответ получаем JSON от сервера SignalR следующего вида:

{
    "Url": "/WebApi/signalr",
    "ConnectionToken": "aksjdjklaslk/knasndbhulqhfgsykgdfyky23fsd/lq3grbhasdbsjkhrhvkqlildfhsjlfdo",
    "ConnectionId": "ajskdhjd-1i23ukv-aisyd-1289-fildshn1823",
    "KeepAliveTimeout": 20,
    "DisconnectTimeout": 30,
    "ConnectionTimeout": 110,
    "TryWebSockets": true,
    "ProtocolVersion": "1.5",
    "TransportConnectTimeout": 5,
    "LongPollDelay": 0
}

Url — адрес куда надо делать следующий запрос (константа) ConnectionId — это UUIDv4 ConnectionToken — токен для подключения, его надо будет передать ProtocolVersion — версия, ее тоже надо будет передать

Остальное нас не интересует

  1. Создаем веб-сокет GET-запросом на адрес wss://asurso.ru/WebApi/signalr/connect с параметрами: (URL-параметры, через &)

transport=webSockets clientProtocol=1.5 at=вашЗаголовокAt connectionToken=токенПодключения connectionData=[{"name":"queuehub"}]

Параметр tid не обязателен.

Важно!! обязательно в заголовке в запросе на создание веб-сокета нужно послать куки ESRNSec токен, иначе получим ответ 403 Forbidden от SignalR.

Также между получением токена-подключена и созданием сокета стоит подождать около секунды.

  1. Подключившись к SignalR сразу получаем какие-то крипто-сообщения, их надо игнорировать. С открытым сокетом параллельно отправляем обычный GET-запрос на https://asurso.ru/WebApi/signalr/start со следующими параметрами:

transport: webSockets clientProtocol: 1.5 at: вашAtКлюч connectionToken: вашТокенПодключения connectionData: [{"name":"queuehub"}]

Обязательно в заголовке отправляем куки ESRNSec, дожидаемся ответа и игнорируем его содержание.

  1. Теперь возвращаемся к веб-сокету и отправляем сообщение {"H":"queuehub","M":"StartTask","A":[13600184],"I":0}. 13600184 — это номер задачи, привязанный к генерации Отчета об успеваемости и посещаемости ученика (PDF версия). Я долго искал в коде сайта, как генерируется ID тасков, но так и не понял. На данный момент можно использовать повторно предыдущие. Исходя из результатов моих экспериментов, другие числа и отсутствие параметра A возвращает ошибку {"I":"0","E":"There was an error invoking Hub method 'queuehub.StartTask'."}, такую же ошибку получаем если отправить сообщение до третьего шага.

После успешной отправки получим ответ {"I":"0"} (но это не точно), потом {"C":"s-0,...","M":[{"H":"QueueHub","M":"progress","A":[{"TaskId":13600184,"Status":"В процессе обработки"}]}]} и наконец {"C":"s-0,1DBE6D4","M":[{"H":"QueueHub","M":"complete","A":[{"TaskId":13600184,"Data":"..."}]}]}. Именно Data и будет ID файла. Но не торопитесь получать его — мы еще не уточнили формат (html или pdf) и информацию о генерируемом документе.

  1. Делаем POST-запрос на https://asurso.ru/webapi/reports/studenttotal/queue и если хотим получить в итоге PDF, а не HTML (по умолчанию), то добавляем в конце параметр ?output=Pdf.

В теле передаем JSON:

{
  "selectedData": [
    {
      "filterId": "SID",
      "filterValue": "592640",
      "filterText": "Щелочков Виктор"
    },
    {
      "filterId": "PCLID",
      "filterValue": "...",
      "filterText": "..."
    },
    {
      "filterId": "period",
      "filterValue": "2022-01-10T00:00:00.000Z - 2022-05-28T00:00:00.000Z",
      "filterText": "10.01.2022 - 28.05.2022"
    }
  ],
  "params": [
    {
      "name": "SCHOOLYEARID",
      "value": "..."
    },
    {
      "name": "SERVERTIMEZONE",
      "value": 4
    },
    {
      "name": "FULLSCHOOLNAME",
      "value": "..."
    },
    {
      "name": "DATEFORMAT",
      "value": "d\u0001mm\u0001yy\u0001."
    }
  ]
}

params передавать не обязательно, filterText тоже

Не забудем также передать куки ESRNSec и заголовок at.

Ответ дожидаемся и игнорируем.

После этого можем получить результат привычным образом: https://asurso.ru/webapi/files/[fileID]

  1. (Необязательно) Вы можете сами отключиться от веб-сокета или завершить его с помощью сервера SignalR послав еще один POST-запрос на https://asurso.ru/WebApi/signalr/abort с параметрами

transport: webSockets clientProtocol: 1.5 at: вашAtКлюч connectionToken: токенПодключения connectionData: [{"name":"queuehub"}]

Тогда веб-сокет отключится сам по себе.

Форум

Форум открывается в отдельном окне, а HTML-код страницы генерируется на сервере, поэтому с получением данных возникают некоторые сложности: страницу надо парсить и селекторами искать тег table.

В сгенерированной таблице со списком тем никогда не будет открывающегося тега <tr>, только </tr>

С форума ничего нельзя удалить

Время, указанное на странице тоже генерируется на сервере, поэтому часовой пояс всех дат — Европа/Самара (UTC+4:00)

Ответы на задания

POST запрос на https://asurso.ru/webapi/assignments/[assignmentID]/answers?studentId=[studentID] с заголовком at и телом x-www-form-urlencoded =text — для текста (ключа нет), придет 204; для редактирования отправить тот же запрос; для удаления только =

Для прикрепления файла не нужно делать доп. запросов, просто загружаем файл и в теле указываем assignmentId и assignmentFileResult: true

Загрузка файлов

POST на https://asurso.ru/webapi/attachments с заголовком at, content-type: multipart/form-data (самостоятельно в заголовках не указывать, чтобы сгенерировался boundary!!!), тело:

file: (binary)
data: {"name":"empty.txt","description":"-"}

Название может быть любым, а description может быть null. В ответ придет ID файла в plain text

Для удаления сделайте запрос с методом DELETE на https://asurso.ru/webapi/attachments/[attachmentID] и заголовком at.

Другие интересности

  • image \ ESRNSec составляется из множества ID сессий aspnet, разделенных символом &. То есть, переходя по страницам, вы получаете новые ID которые добавляется в один и тот же куки, не знаю зачем это нужно. Это что-то на асповском 😬

  • GET-Запрос на https://asurso.ru/webapi/grade/assignment/types?all=false дает нам расшифровку аббревиатур на сайте

  • GET-Запрос на https://asurso.ru/webapi/educcertificates/student/[id] дает нам информацию о пользователе, но не так просто: для этого нужно составить сложный ESRNSec. А если подставить несуществующий studentID, то получим такое сообщение: Не удалось получить данные о сертификате., не знаю при чем тут сертификат. Это что-то на асурсовсом 😬 А если подставить существующий, то лично я получаю null (кроме собственного studentID)

  • Разные ваши сессии отображаются как разные люди в списке онлайна

  • Сделайте GET-запрос на https://asurso.ru/webapi/attachments/uploadLimits без заголовка at, чтобы получить лимиты на загрузку файла

  • Если в адресе на любую страницу указан параметр at, то его же в теле запроса присылать не нужно, достаточно сделать GET запрос