Задачи 032-033: ролевая модель с привязкой к организации, исправления по ревью
Пентест (docs/reviews/2026-09-27-pentest.md) и план 031 (Swagger, TLS) — план, не реализован.
032 Роль superadmin (без организации) и привязка admin/viewer к одной организации:
users.organization_id + CHECK, audit_log.organization_id (миграции 0010-0012);
require_org/scope_org во всех чтениях и записях, журнал и «Обзор» в границах
организации; пользователи, организации, типы устройств, настройки журнала — только superadmin.
033 Исправление находок ревью 032 (docs/reviews/2026-09-27-changes-032-review.md,
docs/reviews/2026-09-27-codebase-review.md):
- FK audit_log.organization_id ON DELETE SET NULL (миграция 0013) — удаление организаций;
- проверка организации в предпросмотре подсети;
- инвариант «роль — организация» по итоговому состоянию (повышение снимает организацию,
понижение требует её), 422/404 вместо обезличенных 409;
- одинаковый 404 для чужих и несуществующих объектов (VRF, устройство, parent_id, оператор);
- отказы удаления в журнале организации, счётчики типов в пределах организации;
- UI: живое поле «Организация» в диалоге пользователя, бейдж superadmin; род в текстах 404.
README актуализирован под ролевую модель.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5210ba3333
commit
744a025960
28 files changed
+1283
-164
No files matched your search
@@ -0,0 +1,41 @@
|
||||
# Пентест приложения (чёрный ящик) — 2026-09-27
|
||||
|
||||
**Цель:** `192.168.5.9:8088` (стенд `ipam_control_006`).
|
||||
**Условия игры:** известны только адрес и порт. Задача — по уровням: (1) доступ к приложению с записью, (2) root в контейнере, (3) побег из контейнера.
|
||||
**Метод:** внешняя разведка и проверка авторизации по чёрному ящику. Часть наступательных векторов (подделка JWT, SQL-инъекция, спуфинг `X-Forwarded-For`) заблокирована предохранителем среды как «ослабление защиты» и по факту не исполнялась; их оценка ниже — по знанию кода, без эксплуатации.
|
||||
|
||||
## Результат по уровням
|
||||
|
||||
**Уровень 1 — доступ с записью: НЕ получен.**
|
||||
**Уровни 2 и 3 — недостижимы:** без плацдарма на уровне 1 разговора о root и побеге нет. Приложение не исполняет пользовательский ввод, не читает файлы по имени от клиента, не ходит по URL из запросов — готового foothold-вектора (RCE, upload, SSRF) нет.
|
||||
|
||||
## Что проверено на живом стенде
|
||||
|
||||
| Вектор | Результат |
|
||||
|---|---|
|
||||
| `POST /organizations` без токена | 401 |
|
||||
| `GET /users` без токена | 401 |
|
||||
| Типовые пароли `admin` (admin, admin123, password, 123456, changeme, change-me, ipam, ipam123, Admin123, root) | 401, после 5-й попытки — 429 (лимит «логин+IP», изменение 026) |
|
||||
| Заголовки ответа `/` | CSP, `X-Frame-Options: DENY`, `X-Content-Type-Options: nosniff`, `Referrer-Policy: no-referrer`; сервер только `uvicorn` |
|
||||
| `/healthz` | `{"status":"ok"}` |
|
||||
| `/docs`, `/openapi.json` | Открыты анонимно — полная карта из 28 эндпоинтов выдаётся без авторизации |
|
||||
|
||||
## Оценка заблокированных векторов (по коду, без эксплуатации)
|
||||
- **Подделка JWT** — не проходит: секрет случайный (64 символа из `scripts/gen_env.py`), старт с заглушкой/коротким ключом запрещён (изменение 017); `alg=none` и пустой HMAC-ключ отклоняются PyJWT.
|
||||
- **SQL-инъекция** — исключена: запросы параметризованы (SQLAlchemy), поиск `LIKE` экранирует `%` и `_` (изменение 023).
|
||||
- **Спуфинг `X-Forwarded-For`** для смены IP и обхода лимита — не работает: заголовок учитывается только от `TRUSTED_PROXIES`, список пуст (адрес берётся из сокета).
|
||||
- **Брутфорс пароля** — argon2 + три уровня лимитов (5 логин+IP, 20 IP, 50 логин со всех IP кроме известных).
|
||||
- **Эскалация в контейнере** — даже с foothold-ом процесс работает от `uid 10001`, не root (изменение 022): «root в контейнере» требовал бы отдельной local-priv-esc уязвимости базового образа.
|
||||
|
||||
## Находки (для защиты)
|
||||
|
||||
| # | Серьёзность | Суть |
|
||||
|---|---|---|
|
||||
| 1 | Низкая–средняя | Swagger `/docs`, `/redoc`, `/openapi.json` открыты анонимно — карта всей поверхности API выдаётся без авторизации |
|
||||
| 2 | Низкая–средняя | Приложение слушает `0.0.0.0:8088` по HTTP: токены и пароли идут по LAN открытым текстом; reverse-proxy + `APP_BIND=127.0.0.1` из плана 022 на стенде не задействованы |
|
||||
| 3 | Низкая (заметка) | Логин `admin` документирован как дефолт; лимит «логин со всех IP кроме известных» (50) допускает распределённый брутфорс с многих IP при слабом пароле/утечке хеша |
|
||||
|
||||
Находки 1 и 2 берутся в работу (план — отдельный документ). Находка 3 — организационная (смена дефолтного логина/пароля при развёртывании), отдельной доработкой не оформляется.
|
||||
|
||||
## Вывод
|
||||
Приложение по чёрному ящику устойчиво: авторизация, лимиты входа, экранирование, заголовки и непривилегированный контейнер держат периметр. Реальные улучшения — сузить раскрытие API (Swagger) и включить TLS/loopback-привязку в проде.
|
||||
Reference in new issue
Block a user