Пен тест #25

Closed
opened 2026-09-09 01:40:20 +03:00 by NotBigGhost · 3 comments
Owner

Упор на аутентификации и проходу по апи, дальше остальное.

/secure-review

Зависит от #24

Упор на аутентификации и проходу по апи, дальше остальное. /secure-review Зависит от #24
NotBigGhost added this to the 1.37 - смена аутентификации и смежный пен тест milestone 2026-09-09 01:40:20 +03:00
NotBigGhost added the Kind/Security
Priority
Critical
1
labels 2026-09-09 01:40:20 +03:00
NotBigGhost added
Priority
High
2
and removed
Priority
Critical
1
labels 2026-09-09 14:07:04 +03:00
Agent added the
Status
Blocked
1
label 2026-09-09 14:25:36 +03:00
NotBigGhost removed the
Status
Blocked
1
label 2026-09-13 15:37:03 +03:00
Agent self-assigned this 2026-09-13 16:01:58 +03:00
Agent added the
Reviewed
Confirmed
1
label 2026-09-13 16:01:59 +03:00
Collaborator

План выполнения

Пен-тест контура аутентификации и прохода по API после #24 (вход по логину/паролю стал основным
во всех окружениях). Задача исследовательская: код в этом проходе не меняется, результат —
отчёт и заведённые по находкам задачи. Ветка и PR не создаются.

  1. Статический разбор трёх областей:
    • Аутентификация (главное): throttle входа игрока и входа админа, жизненный цикл JWT
      (отзыв при logout / смене пароля), Telegram HMAC, CSRF double-submit, атрибуты cookie.
    • Проход по API / авторизация: наличие get_current_user/get_current_admin на каждом
      эндпойнте, IDOR, доверие к X-Forwarded-For (--forwarded-allow-ips=*).
    • Конфигурация: дефолты SECRET_KEY/ADMIN_PASSWORD, открытость /api/docs, in-memory
      throttle, заголовки безопасности, валидация ввода.
  2. Динамика — локально (одноразовый бэкенд со своей БД, данные dev/prod не затрагиваются):
    throttle игрока, перебор пароля админа, выживание JWT после logout/смены пароля, CSRF, IDOR,
    подмена X-Forwarded-For, открытость /api/docs.
  3. Живьё, неразрушающе на forbidden-stars.ru (когда test-клон поднят): заголовки, TLS,
    /api/docs. Перебор и мутации не выполняются. Прод не трогаем.

Критерии готовности

  • Разобраны все три области; по каждому пункту — вывод (уязвимо / норма / принятый риск) с обоснованием.
  • Динамика выполнена локально, данные не затронуты; коды ответов приложены как evidence.
  • Отчёт с обязательными для Security-задачи разделами «Что проверено» и «Модель угроз» опубликован здесь.
  • На каждую подтверждённую находку заведена отдельная задача (Kind/Security) со ссылкой на #25.
  • Утверждение отчёта и списка задач владельцем.

Ветка: не создаётся (исследовательская задача, кода нет).

## План выполнения Пен-тест контура аутентификации и прохода по API после #24 (вход по логину/паролю стал основным во всех окружениях). Задача исследовательская: **код в этом проходе не меняется**, результат — отчёт и заведённые по находкам задачи. Ветка и PR не создаются. 1. **Статический разбор** трёх областей: - **Аутентификация** (главное): throttle входа игрока и **входа админа**, жизненный цикл JWT (отзыв при logout / смене пароля), Telegram HMAC, CSRF double-submit, атрибуты cookie. - **Проход по API / авторизация**: наличие `get_current_user`/`get_current_admin` на каждом эндпойнте, IDOR, доверие к `X-Forwarded-For` (`--forwarded-allow-ips=*`). - **Конфигурация**: дефолты `SECRET_KEY`/`ADMIN_PASSWORD`, открытость `/api/docs`, in-memory throttle, заголовки безопасности, валидация ввода. 2. **Динамика — локально** (одноразовый бэкенд со своей БД, данные dev/prod не затрагиваются): throttle игрока, перебор пароля админа, выживание JWT после logout/смены пароля, CSRF, IDOR, подмена `X-Forwarded-For`, открытость `/api/docs`. 3. **Живьё, неразрушающе** на `forbidden-stars.ru` (когда test-клон поднят): заголовки, TLS, `/api/docs`. Перебор и мутации не выполняются. Прод не трогаем. **Критерии готовности** - Разобраны все три области; по каждому пункту — вывод (уязвимо / норма / принятый риск) с обоснованием. - Динамика выполнена локально, данные не затронуты; коды ответов приложены как evidence. - Отчёт с обязательными для Security-задачи разделами «Что проверено» и «Модель угроз» опубликован здесь. - На каждую подтверждённую находку заведена отдельная задача (`Kind/Security`) со ссылкой на #25. - Утверждение отчёта и списка задач владельцем. **Ветка:** не создаётся (исследовательская задача, кода нет).
Collaborator

Пен-тест #25 — отчёт

Проверка безопасности контура аутентификации и прохода по API после #24 (вход по
логину/паролю стал основным во всех окружениях). Упор — на аутентификацию, как задано в теле.

Находки уровня High поданы как класс проблемы + условия, без готового пошагового эксплойта.
Код в этом проходе не менялся — на каждую находку заведена отдельная задача.

Резюме

# Находка Severity Класс
F1 Вход администратора без защиты от перебора High Brute-force
F2 JWT не отзывается при выходе и смене пароля High Session mgmt
F3 Обход throttle и подмена IP через X-Forwarded-For High Spoofing / rate-limit bypass
F4 Нет fail-fast на дефолтных SECRET_KEY / ADMIN_PASSWORD High Misconfiguration
F5 In-memory throttle: сброс при рестарте, неэффективность при >1 воркере Medium Rate-limit durability
F6 Раскрытие: открытая схема API + отсутствие security-заголовков Low Info disclosure
F7 /api/auth/register без throttle + перечисление ников Low Enumeration
Q1 Любой вошедший видит профиль/историю любого игрока по id Info Требует решения владельца

Что проверено

Метод: статический разбор кода аутентификации/авторизации + динамические проверки на
изолированном экземпляре приложения (in-memory БД, данные dev/prod не затрагивались),
плюс неразрушающая проверка edge (TLS/заголовки) на test-домене. Мутаций и перебора на живых
контурах не выполнялось. Базовый прогон: pytest — 131 passed.

Область и вердикты:

  • Вход игрока по паролю — норма: throttle срабатывает (5 неудач на пару IP+логин → 429 на
    6-й попытке), единая ошибка INVALID_CREDENTIALS и для неизвестного логина, и для неверного
    пароля (нет user-enumeration), фиктивный хеш выравнивает тайминг, вход только role='player'.
  • Вход администратора — F1: не проходит через throttle.
  • Жизненный цикл JWT — F2: нет отзыва.
  • CSRF (double-submit) — норма: мутация с сессией без X-CSRF-Token → 403 CSRF_FAILED,
    с корректным токеном → 200; токен перевыдаётся при потере cookie.
  • Telegram-вход — норма: HMAC-SHA256 ключом SHA256(bot_token), hmac.compare_digest,
    проверка свежести auth_date (сутки). Привязка требует авторизации и CSRF, занятый Telegram → 409.
  • Cookie — норма: httponly на сессионных, secure выводится из окружения, samesite=lax,
    путь админ-cookie ограничен /api/admin; CSRF-cookie httponly=False (нужно для double-submit).
  • Авторизация мутаций групп/партий — норма: везде assert_member / assert_can_modify.
  • Валидация ввода — норма: пароль 8–72 байт (короткий и длинный → 422), формат ника, эхо тела
    в 422 без сырых байт (уже чинилось в #52).
  • Доверие к прокси-заголовкам — F3.
  • Конфигурация секретов — F4; раскрытие схемы/заголовки — F6; register — F7.
  • SPA-раздача — норма: обход каталога закрыт (resolve() + проверка _STATIC_DIR in parents).
  • Фронт — норма: JWT только в httpOnly-cookie, в localStorage токенов нет; CSRF из cookie.

Модель угроз

  • Активы: учётные записи игроков и администратора, JWT-сессии (fs_session 7 дней,
    fs_admin 8 ч), партии/статистика, аудит-журнал.
  • Нарушители: (а) неаутентифицированный внешний, (б) вошедший игрок, (в) владелец
    украденного/утёкшего токена, (г) сетевой наблюдатель. Точка входа одна — публичный HTTPS через
    Caddy на VPS → SSH-туннель → приложение (uvicorn 1 воркер).
  • Границы доверия: клиент↔Caddy (TLS терминируется на edge), Caddy↔приложение (через туннель;
    приложение доверяет X-Forwarded-* — см. F3), приложение↔SQLite.
  • Главные векторы: онлайн-перебор пароля админа (F1) — прямой путь к полному контролю;
    бессрочность украденного токена (F2); снятие лимита перебора и порча аудита подставным IP (F3);
    компрометация через дефолтные секреты при небрежном деплое (F4).
  • Вне области этого прохода: DoS/нагрузка, физический доступ к Pi, безопасность цепочки
    поставки образов, соц.инженерия, безопасность самого Telegram-аккаунта игрока.

Находки

F1 — Вход администратора без защиты от перебора (High)

Где: routers/admin.py:36 → services/admin_service.py:authenticate_admin.
Суть: вход игрока обёрнут в login_throttle (auth/password.py), а вход админа — нет.
Пароль администратора — единственный барьер к полному контролю приложения, и его можно
перебирать онлайн без ограничения частоты.
Условия/evidence: 25 последовательных попыток входа с неверным паролем — все 401, ни одного
429. Логина-обёртки с лимитом на этом пути нет.
Рекомендация: пропустить authenticate_admin через тот же login_throttle (ключи по IP и по
username), с отдельным, более строгим лимитом; фиксировать серию неудач в аудите.

F2 — JWT не отзывается при выходе и смене пароля (High)

Где: routers/auth.py:118 (logout), core/security.py, auth/deps.py,
routers/users.py:121 (смена пароля), routers/admin.py:115 (сброс админом).
Суть: logout лишь просит браузер удалить cookie; серверного списка отозванных токенов или
версии токена нет — decode_token проверяет только подпись/aud/exp. Украденный или оставшийся
токен остаётся валидным до истечения TTL (игрок — 7 дней), в том числе после выхода и
после смены пароля. Сценарий сброса «забытого» пароля админом это тоже не закрывает: если
аккаунт был скомпрометирован, старые сессии злоумышленника продолжают работать.
Условия/evidence: старый токен, предъявленный вручную после logout → GET /api/users/me = 200;
то же после смены пароля → 200.
Рекомендация: ввести token_version у пользователя (claim в JWT, сверка в deps), инкремент
при смене пароля и при «выйти со всех устройств»; либо denylist по jti / отсечение по
iat < password_changed_at.

F3 — Обход throttle и подмена IP через X-Forwarded-For (High; подтвердить на живом контуре)

Где: entrypoint.sh (--forwarded-allow-ips="*"), core/security.py:client_ip,
core/ratelimit.py, deploy/vps/Caddyfile.
Суть: uvicorn запущен с --forwarded-allow-ips="*" → доверяет заголовку X-Forwarded-For от
любого пира и берёт из него левое (клиентское) значение
(ProxyHeadersMiddleware.get_trusted_client_address). Caddy добавляет реальный IP справа, поэтому
подставленное клиентом левое значение сохраняется и становится request.client.host. Оба ключа
throttle (login:{ip}:{nickname} и login-ip:{ip}) содержат этот IP — значит ротация
X-Forwarded-For на каждый запрос полностью снимает лимит перебора пароля
. Тот же IP пишется в
аудит (audit_service) — журнал источника подделывается (анти-форензика).
Условия/evidence: подтверждено чтением кода uvicorn (левое значение при *) и Caddyfile
(append XFF). Полную эксплуатацию на живом контуре в рамках задачи не выполняли (без перебора на
проде/тесте) — рекомендуется подтвердить на test-клоне.
Рекомендация: доверять X-Forwarded-For только фактическому прокси (loopback / адрес туннеля),
а на Caddy перезаписывать заголовок реальным пиром (header_up X-Forwarded-For {remote_host})
вместо append; тогда client_ip снова достоверен и для throttle, и для аудита.

F4 — Нет fail-fast на дефолтных SECRET_KEY / ADMIN_PASSWORD (High, misconfiguration)

Где: core/config.py (дефолты secret_key="change-me-dev-secret-not-for-production",
admin_password="change-me-admin-password"), bootstrap.py:_ensure_admin.
Суть: приложение стартует в production с дефолтными секретами без предупреждения. bootstrap
отклоняет лишь пустой пароль админа, но не дефолтный. Деплой, скопировавший .env.example
дословно, получает: известный всем ключ подписи JWT (→ подделка любого токена, включая админский)
и известный пароль администратора.
(Секреты прода в рамках задачи не читались — проверяется само отсутствие защиты от дефолта.)
Рекомендация: при is_production падать на старте, если SECRET_KEY/ADMIN_PASSWORD равны
дефолтам или слишком коротки; вынести проверку в config/bootstrap.

F5 — In-memory throttle: сброс при рестарте и неэффективность при масштабировании (Medium)

Где: core/ratelimit.py.
Суть: счётчики живут в памяти процесса. Любой рестарт (в т.ч. деплой) обнуляет окно 15 минут;
при уходе от --workers 1 лимит делится между воркерами. В сумме с F3 (подставной IP) онлайн-
перебор пароля практически не ограничен.
Рекомендация: при переходе на несколько воркеров — вынести счётчики во внешний стор (Redis);
добавить глобальный лимит на аккаунт, не зависящий от IP.

F6 — Раскрытие: открытая схема API + отсутствие security-заголовков (Low)

Где: main.py:157 (openapi_url/docs_url/redoc_url), deploy/vps/Caddyfile.
Суть: /api/openapi.json (68 путей), /api/docs, /api/redoc доступны без авторизации во всех
окружениях — облегчает разведку поверхности API. На edge (Caddy) не выставляются HSTS, CSP,
X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Permissions-Policy; в ответе виден
Server: Caddy.
Условия/evidence: локально GET /api/openapi.json = 200 (68 путей); на test-домене в ответе
только Cache-Control: no-store и Server: Caddy, security-заголовков нет (TLS-сертификат валиден).
Рекомендация: закрыть docs/openapi в production (или под админ-сессией); добавить security-
заголовки в общий сниппет (edge) Caddyfile; убрать/переопределить Server.

F7 — /api/auth/register без throttle + перечисление ников (Low)

Где: routers/auth.py:34, routers/users.py:258 (nickname-available), :268 (search).
Суть: регистрация не ограничена по частоте (спам аккаунтов); существующий ник → 409 NICKNAME_TAKEN, что даёт перечисление занятых ников. Авторизованные nickname-available и search
позволяют то же вошедшему. Вход (login) даёт единую ошибку — там enumeration нет.
Рекомендация: throttle на register (по IP); перечисление ников через занятость — принять как
малый риск (ники и так публичны в топе) либо смягчить.

Q1 — Профиль и история любого игрока доступны любому вошедшему (Info, требует решения)

Где: routers/users.py:211 (/{id}/profile), :220 (/{id}/matches).
Суть: эндпойнты защищены только get_current_user — вошедший может перебором по id получить
профиль и историю партий любого игрока (историю ограничивает «витрина владельца» all/best). Аноним
получает 401 — это норма. Вероятно, задумано (соц.функция сайта), но фиксирую для явного решения
владельца, стоит ли ограничивать.


Статус: Status/In Review — жду вашего утверждения отчёта и списка задач. После утверждения
задачи готовы к приоритизации (Priority/* расставляете вы).

# Пен-тест #25 — отчёт Проверка безопасности контура **аутентификации и прохода по API** после #24 (вход по логину/паролю стал основным во всех окружениях). Упор — на аутентификацию, как задано в теле. Находки уровня High поданы как **класс проблемы + условия**, без готового пошагового эксплойта. Код в этом проходе не менялся — на каждую находку заведена отдельная задача. ## Резюме | # | Находка | Severity | Класс | |---|---------|----------|-------| | F1 | Вход администратора без защиты от перебора | **High** | Brute-force | | F2 | JWT не отзывается при выходе и смене пароля | **High** | Session mgmt | | F3 | Обход throttle и подмена IP через `X-Forwarded-For` | **High** | Spoofing / rate-limit bypass | | F4 | Нет fail-fast на дефолтных `SECRET_KEY` / `ADMIN_PASSWORD` | **High** | Misconfiguration | | F5 | In-memory throttle: сброс при рестарте, неэффективность при >1 воркере | Medium | Rate-limit durability | | F6 | Раскрытие: открытая схема API + отсутствие security-заголовков | Low | Info disclosure | | F7 | `/api/auth/register` без throttle + перечисление ников | Low | Enumeration | | Q1 | Любой вошедший видит профиль/историю любого игрока по id | Info | Требует решения владельца | ## Что проверено **Метод:** статический разбор кода аутентификации/авторизации + динамические проверки на **изолированном** экземпляре приложения (in-memory БД, данные dev/prod не затрагивались), плюс неразрушающая проверка edge (TLS/заголовки) на test-домене. Мутаций и перебора на живых контурах не выполнялось. Базовый прогон: `pytest` — **131 passed**. Область и вердикты: - **Вход игрока по паролю** — норма: throttle срабатывает (5 неудач на пару IP+логин → 429 на 6-й попытке), единая ошибка `INVALID_CREDENTIALS` и для неизвестного логина, и для неверного пароля (нет user-enumeration), фиктивный хеш выравнивает тайминг, вход только `role='player'`. - **Вход администратора** — **F1**: не проходит через throttle. - **Жизненный цикл JWT** — **F2**: нет отзыва. - **CSRF (double-submit)** — норма: мутация с сессией без `X-CSRF-Token` → 403 `CSRF_FAILED`, с корректным токеном → 200; токен перевыдаётся при потере cookie. - **Telegram-вход** — норма: HMAC-SHA256 ключом `SHA256(bot_token)`, `hmac.compare_digest`, проверка свежести `auth_date` (сутки). Привязка требует авторизации и CSRF, занятый Telegram → 409. - **Cookie** — норма: `httponly` на сессионных, `secure` выводится из окружения, `samesite=lax`, путь админ-cookie ограничен `/api/admin`; CSRF-cookie `httponly=False` (нужно для double-submit). - **Авторизация мутаций групп/партий** — норма: везде `assert_member` / `assert_can_modify`. - **Валидация ввода** — норма: пароль 8–72 байт (короткий и длинный → 422), формат ника, эхо тела в 422 без сырых байт (уже чинилось в #52). - **Доверие к прокси-заголовкам** — **F3**. - **Конфигурация секретов** — **F4**; **раскрытие схемы/заголовки** — **F6**; **register** — **F7**. - **SPA-раздача** — норма: обход каталога закрыт (`resolve()` + проверка `_STATIC_DIR in parents`). - **Фронт** — норма: JWT только в httpOnly-cookie, в `localStorage` токенов нет; CSRF из cookie. ## Модель угроз - **Активы:** учётные записи игроков и **администратора**, JWT-сессии (`fs_session` 7 дней, `fs_admin` 8 ч), партии/статистика, аудит-журнал. - **Нарушители:** (а) неаутентифицированный внешний, (б) вошедший игрок, (в) владелец украденного/утёкшего токена, (г) сетевой наблюдатель. Точка входа одна — публичный HTTPS через Caddy на VPS → SSH-туннель → приложение (uvicorn 1 воркер). - **Границы доверия:** клиент↔Caddy (TLS терминируется на edge), Caddy↔приложение (через туннель; приложение доверяет `X-Forwarded-*` — см. F3), приложение↔SQLite. - **Главные векторы:** онлайн-перебор пароля админа (F1) — прямой путь к полному контролю; бессрочность украденного токена (F2); снятие лимита перебора и порча аудита подставным IP (F3); компрометация через дефолтные секреты при небрежном деплое (F4). - **Вне области этого прохода:** DoS/нагрузка, физический доступ к Pi, безопасность цепочки поставки образов, соц.инженерия, безопасность самого Telegram-аккаунта игрока. --- ## Находки ### F1 — Вход администратора без защиты от перебора (High) **Где:** `routers/admin.py:36` → `services/admin_service.py:authenticate_admin`. **Суть:** вход игрока обёрнут в `login_throttle` (`auth/password.py`), а вход админа — нет. Пароль администратора — единственный барьер к полному контролю приложения, и его можно перебирать онлайн без ограничения частоты. **Условия/evidence:** 25 последовательных попыток входа с неверным паролем — все `401`, ни одного `429`. Логина-обёртки с лимитом на этом пути нет. **Рекомендация:** пропустить `authenticate_admin` через тот же `login_throttle` (ключи по IP и по `username`), с отдельным, более строгим лимитом; фиксировать серию неудач в аудите. ### F2 — JWT не отзывается при выходе и смене пароля (High) **Где:** `routers/auth.py:118` (`logout`), `core/security.py`, `auth/deps.py`, `routers/users.py:121` (смена пароля), `routers/admin.py:115` (сброс админом). **Суть:** `logout` лишь просит браузер удалить cookie; серверного списка отозванных токенов или версии токена нет — `decode_token` проверяет только подпись/`aud`/`exp`. Украденный или оставшийся токен остаётся валидным до истечения TTL (игрок — **7 дней**), в том числе **после выхода** и **после смены пароля**. Сценарий сброса «забытого» пароля админом это тоже не закрывает: если аккаунт был скомпрометирован, старые сессии злоумышленника продолжают работать. **Условия/evidence:** старый токен, предъявленный вручную после `logout` → `GET /api/users/me` = `200`; то же после смены пароля → `200`. **Рекомендация:** ввести `token_version` у пользователя (claim в JWT, сверка в `deps`), инкремент при смене пароля и при «выйти со всех устройств»; либо denylist по `jti` / отсечение по `iat < password_changed_at`. ### F3 — Обход throttle и подмена IP через `X-Forwarded-For` (High; подтвердить на живом контуре) **Где:** `entrypoint.sh` (`--forwarded-allow-ips="*"`), `core/security.py:client_ip`, `core/ratelimit.py`, `deploy/vps/Caddyfile`. **Суть:** uvicorn запущен с `--forwarded-allow-ips="*"` → доверяет заголовку `X-Forwarded-For` от **любого** пира и берёт из него **левое** (клиентское) значение (`ProxyHeadersMiddleware.get_trusted_client_address`). Caddy добавляет реальный IP справа, поэтому подставленное клиентом левое значение сохраняется и становится `request.client.host`. Оба ключа throttle (`login:{ip}:{nickname}` и `login-ip:{ip}`) содержат этот IP — значит **ротация `X-Forwarded-For` на каждый запрос полностью снимает лимит перебора пароля**. Тот же IP пишется в аудит (`audit_service`) — журнал источника подделывается (анти-форензика). **Условия/evidence:** подтверждено чтением кода uvicorn (левое значение при `*`) и Caddyfile (append XFF). Полную эксплуатацию на живом контуре в рамках задачи не выполняли (без перебора на проде/тесте) — рекомендуется подтвердить на test-клоне. **Рекомендация:** доверять `X-Forwarded-For` только фактическому прокси (loopback / адрес туннеля), а на Caddy перезаписывать заголовок реальным пиром (`header_up X-Forwarded-For {remote_host}`) вместо append; тогда `client_ip` снова достоверен и для throttle, и для аудита. ### F4 — Нет fail-fast на дефолтных `SECRET_KEY` / `ADMIN_PASSWORD` (High, misconfiguration) **Где:** `core/config.py` (дефолты `secret_key="change-me-dev-secret-not-for-production"`, `admin_password="change-me-admin-password"`), `bootstrap.py:_ensure_admin`. **Суть:** приложение стартует в `production` с дефолтными секретами без предупреждения. `bootstrap` отклоняет лишь **пустой** пароль админа, но не дефолтный. Деплой, скопировавший `.env.example` дословно, получает: известный всем ключ подписи JWT (→ подделка любого токена, включая админский) и известный пароль администратора. *(Секреты прода в рамках задачи не читались — проверяется само отсутствие защиты от дефолта.)* **Рекомендация:** при `is_production` падать на старте, если `SECRET_KEY`/`ADMIN_PASSWORD` равны дефолтам или слишком коротки; вынести проверку в `config`/`bootstrap`. ### F5 — In-memory throttle: сброс при рестарте и неэффективность при масштабировании (Medium) **Где:** `core/ratelimit.py`. **Суть:** счётчики живут в памяти процесса. Любой рестарт (в т.ч. деплой) обнуляет окно 15 минут; при уходе от `--workers 1` лимит делится между воркерами. В сумме с F3 (подставной IP) онлайн- перебор пароля практически не ограничен. **Рекомендация:** при переходе на несколько воркеров — вынести счётчики во внешний стор (Redis); добавить глобальный лимит на аккаунт, не зависящий от IP. ### F6 — Раскрытие: открытая схема API + отсутствие security-заголовков (Low) **Где:** `main.py:157` (`openapi_url`/`docs_url`/`redoc_url`), `deploy/vps/Caddyfile`. **Суть:** `/api/openapi.json` (68 путей), `/api/docs`, `/api/redoc` доступны без авторизации во всех окружениях — облегчает разведку поверхности API. На edge (Caddy) не выставляются HSTS, CSP, `X-Content-Type-Options`, `X-Frame-Options`, `Referrer-Policy`, `Permissions-Policy`; в ответе виден `Server: Caddy`. **Условия/evidence:** локально `GET /api/openapi.json` = `200` (68 путей); на test-домене в ответе только `Cache-Control: no-store` и `Server: Caddy`, security-заголовков нет (TLS-сертификат валиден). **Рекомендация:** закрыть docs/openapi в `production` (или под админ-сессией); добавить security- заголовки в общий сниппет `(edge)` Caddyfile; убрать/переопределить `Server`. ### F7 — `/api/auth/register` без throttle + перечисление ников (Low) **Где:** `routers/auth.py:34`, `routers/users.py:258` (`nickname-available`), `:268` (`search`). **Суть:** регистрация не ограничена по частоте (спам аккаунтов); существующий ник → `409 NICKNAME_TAKEN`, что даёт перечисление занятых ников. Авторизованные `nickname-available` и `search` позволяют то же вошедшему. Вход (login) даёт единую ошибку — там enumeration нет. **Рекомендация:** throttle на `register` (по IP); перечисление ников через занятость — принять как малый риск (ники и так публичны в топе) либо смягчить. ### Q1 — Профиль и история любого игрока доступны любому вошедшему (Info, требует решения) **Где:** `routers/users.py:211` (`/{id}/profile`), `:220` (`/{id}/matches`). **Суть:** эндпойнты защищены только `get_current_user` — вошедший может перебором по id получить профиль и историю партий любого игрока (историю ограничивает «витрина владельца» all/best). Аноним получает `401` — это норма. Вероятно, задумано (соц.функция сайта), но фиксирую для явного решения владельца, стоит ли ограничивать. --- **Статус:** `Status/In Review` — жду вашего утверждения отчёта и списка задач. После утверждения задачи готовы к приоритизации (`Priority/*` расставляете вы).
Agent added the
Status
In Review
label 2026-09-13 16:18:21 +03:00
Collaborator

Заведённые задачи по находкам (веха «1.37», метка Kind/Security, приоритеты не проставлял — за вами):

  • F1 → #56 — защита от перебора пароля админа (предложено High)
  • F2 → #57 — отзыв JWT при выходе и смене пароля (High)
  • F3 → #58 — доверие к X-Forwarded-For снимает throttle и портит аудит (High)
  • F4 → #59 — fail-fast на дефолтных SECRET_KEY/ADMIN_PASSWORD (High)
  • F5 → #60 — устойчивость throttle (рестарт/многопроцессность/лимит на аккаунт) (Medium)
  • F6 → #61 — закрыть схему API + security-заголовки (Low)
  • F7 → #62 — throttle на регистрацию и перечисление ников (Low)

Q1 (профиль/история любого игрока доступны любому вошедшему) отдельной задачей не заводил —
похоже на осознанный дизайн соц.функции. Скажите, ограничивать ли, и тогда заведу.

Осталось за вами: утвердить отчёт и приоритизировать задачи. Живую эксплуатацию F3 (перебор на
test-клоне) по вашему слову могу подтвердить неразрушающе, когда forbidden-stars.ru будет поднят.

Заведённые задачи по находкам (веха «1.37», метка `Kind/Security`, приоритеты не проставлял — за вами): - F1 → #56 — защита от перебора пароля админа (предложено High) - F2 → #57 — отзыв JWT при выходе и смене пароля (High) - F3 → #58 — доверие к `X-Forwarded-For` снимает throttle и портит аудит (High) - F4 → #59 — fail-fast на дефолтных `SECRET_KEY`/`ADMIN_PASSWORD` (High) - F5 → #60 — устойчивость throttle (рестарт/многопроцессность/лимит на аккаунт) (Medium) - F6 → #61 — закрыть схему API + security-заголовки (Low) - F7 → #62 — throttle на регистрацию и перечисление ников (Low) **Q1** (профиль/история любого игрока доступны любому вошедшему) отдельной задачей **не заводил** — похоже на осознанный дизайн соц.функции. Скажите, ограничивать ли, и тогда заведу. **Осталось за вами:** утвердить отчёт и приоритизировать задачи. Живую эксплуатацию F3 (перебор на test-клоне) по вашему слову могу подтвердить неразрушающе, когда `forbidden-stars.ru` будет поднят.
Sign in to join this conversation.
2 Participants
Notifications
Due Date
No due date set.
Reference: NotBigGhost/ForbiddenStarsApp#25