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*
|
|||
|
|
*Классификация: КОНФИДЕНЦИАЛЬНО*
|