Пентест чужого проекта без явного согласия — это уже не пентест, а взлом, даже если намерения добрые. Здесь было устное «да, смотри». Для серьезных проектов это оформляется письменно.
И сразу хорошая новость: в коде особых дыр нет. Всё найденное — это misconfiguration.
Метаданные HTML раскрыли репозиторий и архитектуру
Первое, что я сделал, — открыл DevTools и посмотрел на HTML страницы. В метатегах нашелся тег с именем команды. Немного OSINT — и у меня на руках публичный репозиторий с проектом, а значит, полная архитектура этого почтового сервиса, а еще пара секретов (к сожалению, нерабочих — за это ребятам респект).
Это называется passive reconnaissance — информация, которую приложение отдает само по себе, без каких-либо запросов к API.
Почему это важно. Публичный репозиторий сам по себе — не проблема. Проблема в том, что атакующий сразу получает карту системы: какие сервисы есть, как они называются, на каких портах слушают, какие зависимости используются. Это существенно сокращает время разведки. Кроме того, в репозиториях часто остаются какие-то секреты, логины и пароли — их можно сохранить для будущей проверки.
Фикс: сделать репозиторий приватным или убрать упоминание команды. Для учебного проекта это ок, но в корпоративном сегменте часто скрывают исходный код — и не просто так.
Бэкенд-сервисы торчат напрямую в интернет
nmap -sV e-smail.ru
Среди открытых портов обнаружились 8081, 8082, 8083 — внутренние Go-микросервисы, доступные напрямую из интернета.
Смысл в следующем. Обычно между интернетом и бэкендом стоит nginx (или другой reverse proxy), который выступает единственной точкой входа. На нём можно настроить rate limiting, фильтрацию заголовков, блокировку по IP, логирование. Если микросервисы торчат напрямую — всё это обходится одним запросом на конкретный порт.
⚠️ Важно: сканирование nmap без разрешения владельца — юридически серая зона во многих странах. Всегда получайте явное согласие.
Фикс: закрыть порты, оставив доступными только 80 и 443. На Linux через ufw или iptables.
Микросервисы должны слушать только на localhost или во внутренней Docker-сети, но не на 0.0.0.0.
Перебор логинов: нет блокировок, разные коды ответа
Проверяю стандартно: как приложение отвечает на несуществующий логин и неверный пароль:
# Несуществующий пользователь
curl -X POST https://e-smail.ru/api/signin
-d '{"email":"nobody@e-smail.ru","password":"test"}'
# 404 Not Found
# Существующий пользователь, неверный пароль
curl -X POST https://e-smail.ru/api/signin
-d '{"email":"admin@e-smail.ru","password":"wrongpass"}'
# 401 Unauthorized
Два разных кода — это точная энумерация пользователей: приложение само говорит, существует ли аккаунт. Именно поэтому я составил словарь потенциальных названий почты и получил пару тестовых аккаунтов.
POST /signup вёл себя так же: возвращал 409 Conflict при попытке зарегистрировать уже существующий email.
При этом rate limiting отсутствует на обоих эндпоинтах — никаких задержек, блокировок после N неудачных попыток, капчи. Брутфорс открыт.
Фикс — два шага:
1. Унифицировать ответы: оба случая — «нет такого пользователя» и «неверный пароль» — должны возвращать одинаковый статус и одинаковое тело:
// Вместо двух разных ответов — один
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid credentials"})
2. Добавить rate limiting — хоть на nginx, хоть в сами микросервисы (а лучше и туда, и туда).
Публичный S3-бакет: все аватарки и ID пользователей
curl "https://e-smail.ru/avatars/?list-type=2" | xmllint --format -
Ответ без авторизации — полный XML-листинг бакета: URL аватарки каждого пользователя, его внутренний user_id, дата загрузки, размер файла.
Бакет с вложениями (attachments) закрыт правильно — доступ через Go API с проверкой прав. Это хорошо. Непонятно, почему avatars оказался публичным.
Фикс: убрать публичный s3:ListBucket из политики бакета. Можно точечно, можно добавить проверку прав или прокси-эндпоинт в микросервис с авторизацией.
SMTP без верификации отправителя: фишинг от admin@e-smail.ru
Это самая серьезная находка.
Немного теории. SMTP — старый протокол, придуманный во времена, когда интернет был маленьким и все друг другу доверяли. Поле MAIL FROM в нём — как поле «От кого» на обычном конверте: написать туда можно что угодно. SPF и DMARC появились как попытка это исправить — но только если они настроены правильно.
У e-smail.ru SPF-запись выглядела так:
v=spf1 mx ~all
~all — это «мягкий фейл»: письма от посторонних не блокируются, а просто помечаются как подозрительные. На практике большинство почтовых клиентов такие письма всё равно доставляют. DMARC не настроен вообще.
Проверяю через nc:
printf "EHLO xrnMAIL FROM:<admin@e-smail.ru>rnRCPT TO:<victim@e-smail.ru>
rnDATArnSubject: Важное сообщениеrnrnТело письмаrn.rnQUITrn" | nc mail.e-smail.ru 25
Postfix принял без вопросов. Письмо доставлено во входящие victim@e-smail.ru с отправителем admin@e-smail.ru — без каких-либо предупреждений.
Реальный сценарий: письмо якобы от admin@e-smail.ru с просьбой сменить пароль или подтвердить аккаунт. Для пользователей сервиса выглядит полностью легитимно. И это не теоретическая уязвимость — сервис уже принимает реальную почту.
Фикс: один DNS-запрос. Сменить ~all на -all в SPF и добавить DMARC:
# SPF
v=spf1 mx -all
# DMARC
_dmarc.e-smail.ru TXT "v=DMARC1; p=reject; rua=mailto:dmarc@e-smail.ru"
p=reject говорит принимающим серверам отклонять письма, не прошедшие проверку. Закрывает проблему полностью.
Итого
Пять находок, и все пять — это misconfiguration, а не уязвимости в коде. Это хорошая новость.
Код при этом написан аккуратно: IDOR на письма и вложения закрыт, JWT-секрет в GitHub Secrets, а не захардкожен, XSS через upload заблокирован. Авторы — молодцы.
Пентест учебных проектов полезен для обеих сторон. Автор получает честный фидбэк до того, как проект попадет в резюме или прод. Пентестер — практику на живом стеке, где есть и реальные находки, и правильно закрытые векторы.
Если делаете учебный проект, найдите кого-то, кто его посмотрит. Но только с явного согласия 🙂
Спасибо за прочтение! Больше про безопасность и разработку — в телеграм-канале Flexible Coding.
P.S. Найденные уязвимости в сервисе уже исправлены, поэтому разбор безопасен.