Несколько недель назад мы с моим другом Беном выиграли JumboHack, Хакатон, проводившийся в Университете Тафтса. Нашим проектом было приложение, которое в стиле Spotify Wrapped генерирует отчёт по питанию в университетских столовых среди студентов на основе данных из раздела «Meal Plan» портала оплаты услуг. Благодаря грамотному продвижению проекта Беном, мы смогли буквально за пару дней привлечь к использованию нашего приложения сотни студентов. В итоге мы победили в общей номинации, а также в номинации «самый завершённый проект» и стали абсолютными победителями конкурса.

Живое демо нашего проекта доступно здесь.

Университет Тафтса, как и Университет Брауна, да и многие другие ВУЗы, использует программное обеспечение компании Atrium с истёкшим сроком действия патента, которое старше нас обоих. В своих маркетинговых заявлениях компания уверяет потенциальных пользователей, что в отличие от легаси-систем это ПО доставит им только «восторженные» чувства. Но, как мы позже увидим, заявления Atrium о том, что их ПО не является «легаси», слишком преувеличено.

Портал оплаты услуг нашего университета называется «JumboCash» и находится по адресу www.jumbocash.net (www обязателен, иначе сайт не загрузится). Тем не менее конечный загружаемый сайт определяется параметром cid (ID кампуса/колледжа/клиента?). Вот портал Университета Брауна, загружаемый через домен Тафтса: www.jumbocash.net/index.php?cid=248. Можете использовать голый IP: 3.131.28.213/index.php?cid=248 во всей его http:// красе.


Ещё одно преступление в веб-разработке: аналогично тому, как я прикрепил это фото (см. в оригинале статьи, – прим. пер.), заголовок портала JumboCash загружается (hotlink) с сайта давно не существующей студенческой организации, размещённого на WordPress.com

По факту этот портал является обёрткой вокруг древнего ПО, гниющая плоть которого проглядывается в неразумных решениях и нестабильных XML-ответах.

При авторизации в JumboCash мы с Беном в первую очередь заметили, что ключ сеанса хранится в виде параметра URL рядом с cid.

https://www.jumbocash.net/index.php?skey=20613b5ef40f04e15ecc5d5f56513b92&cid=233

Пока куки установлен, он игнорируется. Вы можете скопировать URL в приватное окно браузера (или изменить домен на другое учебное заведение), и он продолжит работать. Удаляем параметр skey – работать перестаёт. Изменяем IP адрес, и он… даёт сбой? Очевидно, что какие-то меры безопасности были предприняты, и выглядит всё слишком сложно, чтобы быть результатом невежества разработчика.

▍ Гостевой доступ


Ну а поскольку мы не могли попросить студентов скопировать URL (а с ним и ключ сеанса), нам нужен был другой способ доступа к истории их платежей. Тогда мы обратили внимание на функцию гостевого доступа к JumboCash.

По всей видимости, она предназначалась для того, чтобы родители могли повторно заходить под учётками своих детей и пополнять их баланс. Эта функция позволяет студентам давать доступ к своему аккаунту через их email и устанавливать для гостей «как будто» точный набор разрешений. Я говорю «как будто», потому что гости могут изменять свои разрешения и даже добавлять других гостей. «Отключение» доступа гостя по факту не ограничивает какие-либо возможности, и гости могут повторно включить его сами.



При добавлении новых гостей им отправляется письмо с паролем. Мы купили прекрасный домен jumbo.cash и попросили студентов добавить в свои учётные записи адрес guest@jumbo.cash. В итоге мы перехватывали письма с помощью Postmark и извлекали пароли, после чего выполняли скрипт для авторизации и скрейпинга необходимой информации.


Письма от JumboCash почти одинаковые

Когда я занялся скрейпингом самой истории платежей, нам хоть в чём-то повезло: портал позволял экспортировать данные в формате CSV, что немного упростило задачу. Однако HTML-таблица, которая по факту представляет XML, всё же доставила хлопот.

▍ Последнее испытание


Наше решение добавить гостя на «восхитительный» портал Atrium уже создало достаточно напряжения, и мы решили не просить у студентов их e-mail. Позже стало понятно, что стоило собрать их с самого начала и выдать студентам индивидуальные адреса @jumbo.cash, чтобы всё было гладко.

Теперь же для реализации этого «магического» трюка, нам нужно было определить адреса почты студентов на основе их имён, поскольку портал допускает только такие. После безуспешной попытки заскрейпить каталог Outlook мы обратили внимание на публичный каталог, который содержит имена и письма всех учащихся. Он выполняет простой запрос к JSON API, который является оболочкой вокруг одного или более каталогов LDAP (Lightweight Directory Access Protocol, легковесный протокол доступа к каталогам). К сожалению, это оказалось не так просто.

Портал JumboCash отображает полное имя и фамилию студента по паспорту (например, Бенджамин, а не Бен). По каталогу же поиск можно выполнять только на основе имён, указанных студентами. И хоть в интерфейсе они не отображаются, API возвращает полные имена в формате Lastname, Firstname M.

Сначала мы хотели реализовать перебор каталога по фамилии и использовать функцию levenshtein() стандартной библиотеки PHP для поиска соответствий. Но этот подход оказался проблематичным в отношении распространённых или коротких фамилий – например «Li» не только популярная, но и содержится во множестве имён вроде Julian или Colin, в результате чего возвращаются десятки совпадений. Поскольку пагинация каталога была нарушена (у меня есть догадка «почему», но проблему, похоже, уже исправили), мы не смогли перебирать страницы для поиска совпадений. В итоге в качестве обходного решения мы просто начали пробовать разные форматы вроде Lastname, F. Lastname и так далее, выбирая самое близкое имя. Этот подход сработал…в достаточном числе случаев.

▍ Авторизация


Для фактической авторизации в JumboCash потребовалось два HTTP-запроса и четыре часа реверс-инжиниринга. Я провёл за этим занятием столько времени, что Бен, который уже закончил скрейпить JumboCash для предыдущего проекта, начал работать параллельно над реализацией запасного решения с помощью Puppeteer.

Но сначала поясню, как работает аутентификация. На /login.php отправляется POST-запрос c username и loginphrase, который создаёт ключ сеанса. Затем GET-запрос, отправленный на ту же страницу, «активирует» этот токен. Если второй запрос не выполнить, UI оказывается в странном состоянии «половинчатой авторизации».

Первый запрос вместо заголовка Location возвращает фрагмент JavaScript (<script type='JavaScript'>, если быть точным) для перенаправления на другую страницу. Вторая страница (тоже /login.php только с несколькими параметрами URL) содержит загрузчик и небрежный JS-код.



Спиннер, бесцеремонно скопированный из какого-то туториала:

<!-- загрузчик -->
<div class="loader">
    <div class="inner one"></div>
    <div class="inner two"></div>
    <div class="inner three"></div>
</div>
<h1>Processing...</h1>
<!-- здесь можно указать сообщение -->

Несмотря на всю свою ненормальность, этот танец с бубном не занял бы долгих часов, если бы одни из инструментов нам не соврал. При зондировании сайта через HTTPie отображается HTML, возвращаемый первым POST-запросом, а также выполняется содержащийся в нём JS-код. Однако несмотря на то, что веб-представление HTTPie также совершало запрос (как обычный браузер без наших заголовков), оно не показывало его содержимое.

Мы были озадачены, почему ключи сеансов, генерируемые с помощью запросов HTTPie, работали, а те, что генерировались даже с помощью cURL, нет. Как оказалось, HTTPie тайно выполнял второй «активирующий» запрос. Его веб-представление, пожалуй, не должно выполнять JavaScript, особенно без отображения результата (ещё было бы здорово, если бы веб-представление оставалось отключенным, а не включалось при каждом запросе).

JavaScript при промежуточной «загрузке» экрана, прежде чем опрашивать XML API о статусе skey ответственно проверяет, поддерживает ли браузер «новомодный» API XMLHttpRequest. И хотя с конкатенацией в этом скрипте (показан ниже) ничего не сделаешь, мне посчастливилось отыскать XSS. Это было довольно весело, учитывая количество доменов .edu, на которых работает это ПО.

Мотайте до конца! Там самые ништяки!

var isIE = false;
var req;
var messageHash = -1;
var targetId = -1;
var centerCell;
var size=40;
var increment = 100/size;
var attempts=0;

function pollTaskmaster() {
    attempts++;
    var url = "login-check.php?cid=248&skey=3620efdb97b88d111e8ec5388244e978"; 
    validate(url);
}

function validate(url) {
    if (window.XMLHttpRequest) {
        req = new XMLHttpRequest();
    } else if (window.ActiveXObject) {
        isIE = true;
        req = new ActiveXObject("Microsoft.XMLHTTP");
    }
    req.open("GET", url, true);
    req.onreadystatechange = processPollRequest;
    req.send(null);
}

function processPollRequest() {
    if (req.readyState == 4) {
        if (req.status == 200) {
            var message = req.responseXML.getElementsByTagName("message")[0];
            if (!message) {
              window.location.href='login.php?cid=248&';
              return;
            }
            var remotestatus = message.childNodes[0].nodeValue;
            if (remotestatus == 1 )
               window.location.href='index.php?skey=3620efdb97b88d111e8ec5388244e978&cid=248&';
            if (remotestatus == -1 )
               window.location.href='login.php?cid=248&';
        } else {
            window.status = "No Update ";
        }
        window.status = "Processing request...";    
        setTimeout("pollTaskmaster()", 5000);
    }
}

function gotobadpage() {
  window.location.href='login.php?cid=248&';
}
setTimeout("pollTaskmaster()", 2000);
setTimeout("gotobadpage()", 300000);

▍ Победа в JumboHack



Наш сайт получился отзывчивым, но не особо

Разобравшись с реверс-инжинирингом, мы, наконец, смогли спокойно поразвлечься, создавая сам продукт. Дизайн мы сделали ярким и выразительным, похожим на Spotify Wrapped. Учитывая, сколько времени мы потратили на всё остальное (включая здоровый 8-часовой сон), думаю, получилось неплохо.


Интерфейс приложения работает как сториз в Instagram

Мы расклеили по кампусу листовки, о чём писал Бен в своей статье, и сделали пару анонимных постов в Sidechat под видом обычных пользователей. Реакция на посты оказалась довольно вирусной, а добавленный на каждый слайд URL проекта привлёк сотни студентов, желающих тоже воспользоваться нашим сервисом.

В целом JumboHack получился весёлый! Поездка на метро из Провиденса (всего за $10!), общение с Беном и возвращение домой с наушниками AirPod – прекрасный способ провести выходные по случаю Дня президента.


Листовка через пару недель

Telegram-канал со скидками, розыгрышами призов и новостями IT ?

Комментарии (8)


  1. Dimaasik
    24.03.2024 12:36
    +7

    Университет Тафтса, как и Университет Брауна, да и многие другие ВУЗы, использует программное обеспечение компании Atrium с истёкшим сроком действия патента, которое старше нас обоих. В своих маркетинговых заявлениях компания уверяет потенциальных пользователей, что в отличие от легаси-систем это ПО доставит им только «восторженные» чувства. Но, как мы позже увидим, заявления Atrium о том, что их ПО не является «легаси», слишком преувеличено.

    Мне кажется это болезнь почти всех университетов, вот тоже наглядный пример - сейчас я учусь в КНУ Шевченка (топ 1 университет Украины) и все по которое связанно с университетом просто отвратительного качества и главная проблема - очень сильная бюрократизация процесса изменения и нежелание руководства университета брать на себя ответственность за то в чем не разбираются. В кабинете студента приходится по 3 раза выбирать дисциплины на учебный курс т.к. сервера постоянно ложатся и банально не запоминают выбор. Расписание просто перевели в гугл таблицы (в не очень удобном для использования виде) и тд.

    Учась на факультете информационных технологий могу привести и обратный пример - ПО (так же как и в статье) написанное студентами для студентов. Тут все заточено под простоту и удобство.

    • Бот для прохода на тусовки сделанный буквально за пол банки рево, отлично считывает данные с банковского счета организатора (для получении информации об оплаченном билете),подсчитывает количество выпитого алкоголя, рассчитывает количество алкоголя для закупок, генерирует QR коды для прохода и дает возможность их проверки и верификации волонтерами (чтобы убедиться что человек совершеннолетний и может пить)

    • Из вопроса о расписании - за пару вечеров был собран телеграмм бот для которого спарсили данные из гугл таблиц и внесли даты и дни недели с ссылками на лекции и лабораторные (занятия проводятся дистанционно). Функционал максимально простой - выбираешь номер группы и специальность, ежедневно в указанное время получаешь расписание на сегодня с ссылками на подключение.


    1. Vad344
      24.03.2024 12:36

      нежелание руководства университета брать на себя ответственность за то в чем не разбираются

      Вы считаете, что должно быть иначе?


      1. php7
        24.03.2024 12:36
        +5

        кто не умеет, тот учит ©

        Так что все нормально


    1. domix32
      24.03.2024 12:36
      +1

      нежелание руководства университета брать на себя ответственность за то в чем не разбираются

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


  1. php7
    24.03.2024 12:36
    +5

    Что я только что прочитал?


  1. php7
    24.03.2024 12:36
    +2

    новомодный API XMLHttpRequest

    Это ирония? Или за какой год статья?

    Обн.: Хм, на скрине 2024.
    Софт наверно до времен jQuery.


    1. MaxLevs
      24.03.2024 12:36
      +2

      Скорее, ирония. Автор пишет о древности приложения для оплаты


      1. Bright_Translate Автор
        24.03.2024 12:36

        Возьму в кавычки для ясности