Введение
Портал участников Harbor позволяет восстановить учётную запись, если пароль забыт. Письмо содержит временный токен: владение им должно разрешать ровно одну смену пароля одной записи. Поддержка обнаружила, что ссылки можно использовать повторно и менять пароль чужой записи.
Сначала вы восстановите тестовую запись обычным способом, увидите опасное поведение и исправите небольшой обработчик Python. Опыт Python не нужен: функции и условия объясняются до редактирования. Нужно уметь вводить команду в терминале и переключать вкладки.
Portal Web открывает приложение, Mailbox Web — имитацию почты, App Server — подготовленный код. Всё работает на одном независимом учебном экземпляре. Внешняя почта не отправляется. Токены, сравнение времени, транзакции, смена паролей и вход настоящие; маленькое приложение не является промышленной системой идентификации.
Восстановить запись обычным способом
На этом шаге вы перейдёте по письму и войдёте с новым паролем, определив нормальное поведение, которое должно сохраниться после исправления.
Чтобы начать этот шаг, выберите вкладку Portal Web в интерфейсе лабораторной работы.
Учётная запись обозначает человека, а пароль подтверждает возможность входа. Если пароль забыт, ссылка временно разрешает его сменить. Токен ссылки — не сам пароль. В локальной почте можно действовать от имени владельцев вымышленных записей maya и noah. Обе начинают с StartPass42!. Используйте только учебные записи.

Перейдите на вкладку Portal Web. Найдите Request a reset link. Введите maya в Recovery account и нажмите Send reset link. Ответ: Link requested. Open Local Mailbox. Перейдите на вкладку Mailbox Web. Если показана форма сброса или результат, выберите Local Mailbox, чтобы вернуться к списку писем. Обновите список: новое письмо окажется сверху. В нём указаны получатель, ссылка Open reset link и строка токена.

Используйте ссылку и токен, показанные сейчас на вашей вкладке Mailbox Web; значения токенов различаются между средами.
Нажмите Open reset link в этом письме. В той же вкладке откроется форма с заполненными Token и Reset account. Не меняйте их; введите FirstPass42! в New password и нажмите Reset password. Ожидаются Reset accepted и HTTP 200.

HTTP — обмен запросами и ответами между браузером и сервером. Здесь 200 означает принятие, 403 — отказ, 500 — ошибку приложения. Ошибка соединения отличается от защитного отказа.
Перейдите на вкладку Portal Web. Если показан результат запроса, выберите Back to portal, чтобы снова открыть формы. Используйте Sign in: Login account maya, Password FirstPass42!, затем Sign in. Ожидайте Login accepted. Работающий сброс меняет настоящий пароль, а не только надпись на странице.
Потеряв ссылку, запросите новую той же формой и используйте последнее письмо. Срок действия — 30 минут. Опечатка или истечение срока не требуют перезапуска среды.
Наблюдать нарушение границы
На этом шаге вы выполните одноразовые демонстрационные изменения, чтобы различить владельца, срок действия и однократное разрешение токена.
Чтобы начать этот шаг, выберите вкладку Portal Web в интерфейсе лабораторной работы.
Браузер отправляет поля с именами и значениями. Пользователь может менять их, поэтому сервер не должен доверять Reset account лишь потому, что письмо заполнило его. На сервере есть отдельная запись:
| Поле | Значение |
|---|---|
account |
Запись, которой разрешён этот токен |
expires_at |
Последнее допустимое время в секундах от фиксированного начала отсчёта |
used |
0 до использования, 1 после |
Новый токен действует только для владельца до истечения срока. Успешный сброс переводит его в использованное состояние без возможности повторного применения. Неиспользованный токен также может просрочиться. Ни использованный, ни просроченный токен не разрешает следующую смену.
Повторно используйте одну ссылку
Перейдите на вкладку Portal Web, выберите Back to portal, если показан результат, и запросите новую ссылку для maya. Перейдите на вкладку Mailbox Web. Если показана форма сброса или результат, выберите Local Mailbox, чтобы вернуться к списку писем. Обновите список, откройте новую ссылку и сбросьте пароль maya на DemoPass42!. На странице результата во вкладке Mailbox Web выберите Local Mailbox, чтобы вернуться к списку. Снова откройте ссылку из того же письма и задайте maya пароль ReplayDemo42!. Оба запроса возвращают Reset accepted. Второй должен быть отклонён: это повторное использование, или replay, разрешения, которое должно было исчерпаться.
Попробуйте другой аккаунт
На вкладке Mailbox Web выберите Local Mailbox и снова откройте ссылку из того же письма. Измените Reset account на noah, New password на CrossDemo42! и нажмите Reset password. Запрос принят. Перейдите на вкладку Portal Web и выберите Back to portal, чтобы открыть форму входа. Войдите как noah с CrossDemo42!. Ссылка для maya изменила noah: сервер не привязал токен к аккаунту.
Попробуйте просроченный токен
Оставайтесь на вкладке Portal Web и выберите Back to portal, чтобы снова открыть форму сброса. Введите expired-demo в Token, maya в Reset account и ExpiredDemo42! в New password. Этот подготовленный токен истёк за час до настройки среды. Нажмите Reset password. Он тоже принят: сервер проигнорировал время. Для наблюдения ошибки ждать таймер не нужно.
Эти записи предназначены для одноразовой демонстрации, а тесты исправления используют чистые копии. Оставьте наблюдаемое состояние; напрямую редактировать базу не требуется.
Связать и погасить токен
На этом шаге вы исправите небольшую серверную функцию и проверите её, не меняя действующие демонстрационные записи.
Чтобы начать этот шаг, выберите вкладку App Server в интерфейсе лабораторной работы.
Перейдите на вкладку App Server. Начальный каталог — /home/labex/project/reset. cat выводит текстовый файл; имя файла — аргумент команды. Прочитайте обработчик:
Перед выполнением следующей команды перейдите на вкладку App Server.
cat handler.py
Первая строка импортирует две готовые вспомогательные функции. def определяет функцию; имена в скобках — параметры, входные данные одного вызова. eligible(row, account, now) получает сохранённую запись токена, переданный аккаунт и время сервера. row["account"] читает поле. row is None означает отсутствие найденного токена. Блок if с отступом выполняется только при истинном условии; return False отказывает, а return True разрешает. Здесь блок Python задают четыре начальных пробела.
Сейчас функция возвращает True для любого существующего токена, что объясняет три уязвимости. Кроме того, reset вызывает unsafe_reset, меняющую пароль без погашения токена.
Исправление требует всех условий одновременно. == сравнивает значения, > означает больше, а and требует истинности обеих сторон. or разрешило бы любую сторону, позволяя верному свойству скрыть неверное. Будущий срок больше now; равенство уже означает истечение. used == 0 допускает только неиспользованный токен.
Готовая consume_and_reset начинает транзакцию SQLite, читает актуальный токен, вызывает вашу eligible и совместно фиксирует пароль и used = 1. Это атомарность: изменения успешно выполняются вместе. Блокировка ставится до чтения, чтобы два конкурирующих запроса не увидели одновременно неиспользованный токен. Реализовывать блокировки и хеширование паролей не нужно.
Откройте файл терминальным редактором nano:
nano handler.py
Замените содержимое следующим кодом. Стрелками и Backspace/Delete можно удалить старое и вставить полную замену. Точно сохраняйте пробелы и знаки; скобки позволяют разделить условие на строки.
from framework import unsafe_reset, consume_and_reset
def eligible(row, account, now):
if row is None:
return False
return (row["account"] == account
and row["expires_at"] > now
and row["used"] == 0)
def reset(db, token, account, password):
return consume_and_reset(db, token, account, password, eligible)
Сохраните через Ctrl+O, подтвердите handler.py клавишей Enter и выйдите через Ctrl+X. Каждый Web-запрос читает сохранённый код, поэтому перезапуск не нужен. При HTTP 500 снова откройте файл и сравните отступы, скобки и кавычки.
python3 запускает программу Python. Проверяющая программа принимает путь обработчика и запускает отдельное HTTP-приложение с временными данными SQLite. Она проверяет настоящий код, не исправляет его и не меняет действующие записи:
python3 /opt/reset-lab/check.py handler.py
Ожидаются одиннадцать строк PASS, в том числе valid reset accepted, replay denied и competing use has exactly one winner. FAIL называет нарушенное свойство. Отказ на все запросы провалит нормальный сброс и вход. Изменение браузерных полей или действующей базы не исправляет код, проверяемый на свежих данных. При необходимости снова откройте nano handler.py, исправьте, сохраните и повторите ту же проверку.
Доказать работоспособность восстановления
На этом шаге вы используете исправленный действующий сервис и сопоставите его результаты с изолированными проверками безопасности.
Чтобы начать этот шаг, выберите вкладку Portal Web в интерфейсе лабораторной работы.
Используйте свежую ссылку один раз
Перейдите на вкладку Portal Web, выберите Back to portal, если показан результат, и запросите свежую ссылку для maya. Перейдите на вкладку Mailbox Web. Если показана форма сброса или результат, выберите Local Mailbox, чтобы вернуться к списку писем. Обновите список, откройте новую ссылку и задайте maya пароль ResetSafe42!. Ожидайте Reset accepted и HTTP 200. На вкладке Mailbox Web выберите Local Mailbox, чтобы вернуться к списку, затем снова откройте ссылку из того же письма и попробуйте ReplayAgain42! для maya. Ожидайте Reset denied и HTTP 403.

Сохраните неиспользованную ссылку владельца
Перейдите на вкладку Portal Web, выберите Back to portal, если показана страница результата, и запросите ещё одну свежую ссылку для maya. Перейдите на вкладку Mailbox Web, выберите Local Mailbox, обновите список и откройте новую ссылку. Измените Reset account на noah и попробуйте BlockedCross42!. Ожидайте Reset denied. Этот отказ не должен расходовать неиспользованную ссылку владельца: выберите Local Mailbox на вкладке Mailbox Web, снова откройте то же письмо, оставьте maya в Reset account и задайте ResetSafe42!. Ожидайте успех.
Отклоните просроченный токен и сохраните вход
На вкладке Mailbox Web выберите Back to portal, чтобы открыть форму сброса. Попробуйте Token expired-demo, Reset account maya и New password BlockedExpired42!. Ожидайте Reset denied. Затем перейдите на вкладку Portal Web и выберите Back to portal, чтобы открыть форму входа. Войдите как maya с ResetSafe42!. Должно появиться Login accepted: неудачные попытки не изменили пароль. Чтобы проверить и noah, снова выберите Back to portal и войдите с демонстрационным паролем CrossDemo42!; исправленная конечная точка сохраняет существующие посторонние аккаунты.
Перейдите на вкладку App Server. Повторите регрессионные проверки с временными данными:
Перед выполнением следующей команды перейдите на вкладку App Server.
python3 /opt/reset-lab/check.py handler.py
Все одиннадцать должны пройти, включая два одновременных использования с одним успешным сбросом. Повторение не меняет действующие записи. Нужно сохранить разрешённое восстановление и запретить злоупотребление; отключённая страница или постоянный отказ не являются исправлением.
Приложение имитирует доставку и упрощает идентификацию. В промышленной системе нужны также безопасная почта, ограничения частоты, управление сессиями и политика паролей — вне данного упражнения. Рекомендации OWASP дают более широкий контекст.
Резюме
Вы выполнили настоящий сброс, показали повторное использование, смену чужого пароля и игнорирование срока, затем исправили небольшой обработчик Python. Сохранённый владелец, будущий срок, неиспользованное состояние и атомарное погашение работают вместе. Одноразовые HTTP-тесты доказывают нормальное восстановление, запрет нарушений, неизменность других записей и одного победителя при конкуренции. Задание портала сотрудников применяет эти правила к другому неполному условию.



