260 lines
10 KiB
Markdown
260 lines
10 KiB
Markdown
# ОТЧЁТ О БЕЗОПАСНОСТИ
|
||
## SeaFare Montana (seafare-montana.duckdns.org) — 28.02.2026
|
||
|
||
---
|
||
|
||
## Executive Summary
|
||
|
||
- **Находок: 8** (Критические: 0 | Высокие: 2 | Средние: 4 | Низкие: 2)
|
||
- **Общая оценка: Удовлетворительная**
|
||
- Критических уязвимостей не найдено. Основные проблемы: утечка внутренней информации через `/health`, dev-CORS конфиг в production, отсутствие email-верификации. Финансовые endpoints (wallet, subscription) защищены корректно.
|
||
|
||
---
|
||
|
||
## Scope
|
||
|
||
| Параметр | Значение |
|
||
|----------|----------|
|
||
| **Цель** | https://seafare-montana.duckdns.org |
|
||
| **IP** | 89.19.208.158 |
|
||
| **Приложение** | SeaFare Montana — Maritime Logistics AI |
|
||
| **Стек** | nginx/1.24.0 (Ubuntu) → Flask (Python) + PostgreSQL |
|
||
| **Интеграции** | Google OAuth, Telegram Bot, AISStream, Digitraffic, USDT TRC20 (Tron) |
|
||
| **Frontend** | Vanilla HTML/JS + Leaflet.js (карты) |
|
||
| **Авторизация** | Flask itsdangerous signed tokens (Bearer) |
|
||
| **Период** | 28.02.2026 |
|
||
| **Тип** | Black-box → Grey-box |
|
||
| **Авторизация на тест** | Подтверждена владельцем |
|
||
|
||
---
|
||
|
||
## Результаты сканирования портов
|
||
|
||
| Порт | Сервис | Статус |
|
||
|------|--------|--------|
|
||
| **80** | HTTP (nginx) | **OPEN** (→ redirect to HTTPS) |
|
||
| **443** | HTTPS (nginx) | **OPEN** |
|
||
| 22 | SSH | closed |
|
||
| 3306 | MySQL | closed |
|
||
| 5432 | PostgreSQL | closed |
|
||
| 6379 | Redis | closed |
|
||
| 27017 | MongoDB | closed |
|
||
| 5050 | Backend (Flask) | closed |
|
||
| 8080-9200 | Все прочие (21 порт) | closed |
|
||
|
||
**Вердикт**: минимальная поверхность атаки. Только HTTP/HTTPS наружу.
|
||
|
||
---
|
||
|
||
## API Attack Surface
|
||
|
||
**Открытые эндпоинты (без auth):**
|
||
| Эндпоинт | Данные |
|
||
|----------|--------|
|
||
| `GET /health` | Имя сервиса, версия, DB, users count, AIS status |
|
||
| `GET /api/v1/auth/config` | Google OAuth Client ID |
|
||
| `GET /api/v1/subscription/plans` | Планы подписки с ценами |
|
||
| `GET /api/v1/ports/search?q=` | Поиск портов (116K+) |
|
||
| `POST /api/v1/auth/register` | Открытая регистрация |
|
||
| `POST /api/v1/auth/login` | Логин |
|
||
|
||
**Авторизированные эндпоинты (21+):**
|
||
auth/me, profile, chat/stream, chat/history, map/vessels, wallet, wallet/check, wallet/withdraw, wallet/withdrawals, subscription/upgrade, purchased-contacts, telegram/status, telegram/link, telegram/unlink, admin/revenue, admin/costs
|
||
|
||
---
|
||
|
||
## Находки
|
||
|
||
---
|
||
|
||
### [HIGH-001] Health endpoint — массивная утечка внутренней информации
|
||
|
||
- **Severity**: High (CVSS 7.2)
|
||
- **Категория**: Information Disclosure (OWASP A01:2021)
|
||
- **CWE**: CWE-200
|
||
|
||
**Описание**: Публичный `/health` раскрывает:
|
||
- Имя сервиса: "SeaFare API" v3.30.0
|
||
- Тип БД: PostgreSQL
|
||
- Количество пользователей: 4 (до теста)
|
||
- AISStream: статус подключения, bounding boxes, reconnect count, thread status
|
||
- Digitraffic: статус конфигурации
|
||
|
||
**POC:**
|
||
```bash
|
||
curl -s https://seafare-montana.duckdns.org/health
|
||
# → {"db":"ok","db_mode":"postgres","users":4,"version":"3.30.0",
|
||
# "ais":{"aisstream":{"connected":true,"tracked_mmsis":0,...}}}
|
||
```
|
||
|
||
**Impact**: Атакующий узнаёт версию, стек, кол-во пользователей, состояние внутренних сервисов без авторизации. Упрощает целевую атаку.
|
||
|
||
**Remediation:**
|
||
1. Убрать users count, AIS details, version из публичного health
|
||
2. Оставить только `{"status":"ok"}` для мониторинга
|
||
3. Детальный health — за авторизацией (admin only)
|
||
|
||
---
|
||
|
||
### [HIGH-002] CORS dev-конфиг в production
|
||
|
||
- **Severity**: High (CVSS 6.8)
|
||
- **Категория**: Security Misconfiguration (OWASP A05:2021)
|
||
- **CWE**: CWE-942
|
||
|
||
**Описание**: Заголовок `Access-Control-Allow-Origin: http://127.0.0.1:5050` возвращается на все ответы. Раскрывает:
|
||
1. Внутренний backend на порту 5050
|
||
2. Протокол HTTP (не HTTPS) для внутренних запросов
|
||
3. Dev-конфигурация осталась в production
|
||
|
||
**POC:**
|
||
```bash
|
||
curl -sI https://seafare-montana.duckdns.org/
|
||
# → Access-Control-Allow-Origin: http://127.0.0.1:5050
|
||
```
|
||
|
||
**Remediation:**
|
||
1. Заменить на `Access-Control-Allow-Origin: https://seafare-montana.duckdns.org`
|
||
2. Или динамически проверять Origin по whitelist
|
||
|
||
---
|
||
|
||
### [MED-001] Открытая регистрация без email-верификации
|
||
|
||
- **Severity**: Medium (CVSS 5.3)
|
||
- **CWE**: CWE-287
|
||
|
||
**Описание**: Регистрация мгновенная, без подтверждения email. Аккаунт сразу активен. Можно создавать аккаунты на чужие email.
|
||
|
||
**POC:**
|
||
```bash
|
||
curl -s https://seafare-montana.duckdns.org/api/v1/auth/register \
|
||
-X POST -H "Content-Type: application/json" \
|
||
-d '{"email":"anyone@example.com","password":"123456"}'
|
||
# → {"success":true, "token":"...", "user":{"id":5,"is_active":true}}
|
||
```
|
||
|
||
**Remediation**: Email-верификация перед активацией аккаунта.
|
||
|
||
---
|
||
|
||
### [MED-002] nginx версия раскрыта в заголовках
|
||
|
||
- **Severity**: Medium (CVSS 4.3)
|
||
- **CWE**: CWE-200
|
||
|
||
**Описание**: `Server: nginx/1.24.0 (Ubuntu)` — точная версия и ОС.
|
||
|
||
**Remediation**: `server_tokens off;` в nginx.conf
|
||
|
||
---
|
||
|
||
### [MED-003] Flask itsdangerous token — payload в cleartext
|
||
|
||
- **Severity**: Medium (CVSS 4.5)
|
||
- **CWE**: CWE-311
|
||
|
||
**Описание**: Токен аутентификации содержит base64-encoded payload без шифрования. Любой может декодировать:
|
||
```
|
||
eyJlbWFpbCI6InBlbnRlc3QuLi4iLCJuYW1lIjoiIn0=
|
||
→ {"email":"pentest.seafare@protonmail.com","name":""}
|
||
```
|
||
Подпись (HMAC) защищает от модификации, но email виден.
|
||
|
||
**Remediation**: Рассмотреть шифрование payload или переход на JWT с минимальным payload (только user_id).
|
||
|
||
---
|
||
|
||
### [MED-004] Subscription upgrade — 500 Internal Server Error
|
||
|
||
- **Severity**: Medium (CVSS 4.0)
|
||
- **CWE**: CWE-209, CWE-755
|
||
|
||
**Описание**: `POST /api/v1/subscription/upgrade` возвращает 500 без обработки ошибки. Может раскрывать stack trace в debug-режиме.
|
||
|
||
**POC:**
|
||
```bash
|
||
curl -s https://seafare-montana.duckdns.org/api/v1/subscription/upgrade \
|
||
-X POST -H "Content-Type: application/json" \
|
||
-H "Authorization: Bearer <TOKEN>" \
|
||
-d '{"plan":"pro"}'
|
||
# → 500 Internal Server Error
|
||
```
|
||
|
||
**Remediation**: Обработать ошибку, вернуть корректный JSON с описанием (недостаточно средств / невалидный план).
|
||
|
||
---
|
||
|
||
### [LOW-001] Google OAuth Client ID публично доступен
|
||
|
||
- **Severity**: Low (CVSS 2.5)
|
||
- **CWE**: CWE-200
|
||
|
||
**Описание**: `/api/v1/auth/config` возвращает Google OAuth Client ID. Сам по себе не секрет (нужен для frontend), но позволяет идентифицировать Google Cloud проект.
|
||
|
||
```
|
||
{"google_client_id":"199727561298-...apps.googleusercontent.com"}
|
||
```
|
||
|
||
---
|
||
|
||
### [LOW-002] Wallet API раскрывает реальный blockchain-адрес
|
||
|
||
- **Severity**: Low (CVSS 3.0)
|
||
- **CWE**: CWE-200
|
||
|
||
**Описание**: `/api/v1/wallet` возвращает реальный USDT TRC20 адрес: `TRyQSvbE43RzZuuDaNSpbTvyxzaKqvTybR`. Адрес публично отслеживается на блокчейн-эксплорерах.
|
||
|
||
**Remediation**: Генерировать адрес только при запросе на депозит, не показывать постоянно.
|
||
|
||
---
|
||
|
||
## Что НЕ уязвимо (хорошие практики)
|
||
|
||
| Проверка | Результат |
|
||
|----------|-----------|
|
||
| Admin endpoints (revenue, costs) | **Заблокированы** — "Admin access required" |
|
||
| Privilege escalation (is_admin, plan, balance через profile) | **Заблокировано** — поля не меняют auth-модель |
|
||
| Wallet negative amount | **Заблокировано** — min $2 USDT |
|
||
| SQL Injection (ports/search) | **Не обнаружена** — параметризация |
|
||
| CORS reflection (evil origin) | **Не отражается** |
|
||
| Network ports (DB, Redis, SSH) | **Все закрыты** |
|
||
| HSTS | **Включен** — max-age=31536000 |
|
||
| Security headers | **Все присутствуют** — XFO, XCT, XXP, RP |
|
||
| TLS | **Валидный** сертификат |
|
||
| Flask SECRET_KEY | **Не тривиальный** — 50 common secrets не подошли |
|
||
| Subscription bypass | **Не работает** — план остался free |
|
||
|
||
---
|
||
|
||
## Рекомендации по приоритету
|
||
|
||
### Немедленно (High)
|
||
1. Убрать детальную информацию из `/health` — оставить только `{"status":"ok"}`
|
||
2. Заменить CORS `127.0.0.1:5050` на production домен
|
||
|
||
### В течение недели (Medium)
|
||
3. Добавить email-верификацию при регистрации
|
||
4. `server_tokens off` в nginx
|
||
5. Обработать 500 ошибку на subscription/upgrade
|
||
6. Шифровать payload токена или перейти на JWT
|
||
|
||
### В течение месяца (Low)
|
||
7. Генерировать wallet-адрес on-demand
|
||
8. Ограничить /api/v1/auth/config авторизацией
|
||
|
||
---
|
||
|
||
## Методология
|
||
|
||
- **Инструменты**: curl, Python 3.14
|
||
- **Стандарты**: OWASP Top 10 (2021), OWASP API Security Top 10 (2023)
|
||
- **Просканировано**: 21 порт, 20+ API-эндпоинтов
|
||
- **Тестовый аккаунт**: `pentest.seafare@protonmail.com` (ID: 5) — **удалить**
|
||
- **Ущерб**: нулевой
|
||
|
||
---
|
||
|
||
*Отчёт подготовлен: 28.02.2026*
|
||
*Классификация: КОНФИДЕНЦИАЛЬНО*
|