19
0
0
Скопировать ссылку
Telegram
WhatsApp
Vkontakte
Одноклассники
Назад

Я пропентестил чужой пет-проект — и вот что нашел

Время чтения 8 минут
Нет времени читать?
Скопировать ссылку
Telegram
WhatsApp
Vkontakte
Одноклассники
19
0
0
Нет времени читать?
Скопировать ссылку
Telegram
WhatsApp
Vkontakte
Одноклассники

Привет! Меня зовут Дмитрий Бахтенков, и я разработчик, 2020 занимаюсь коммерческой разработкой на .NET. Знакомый с канала «Иван и цифры» пишет учебный проект — почтовый сервис e-smail. Я заинтересовался им, и мы договорились о пентесте этого сервиса: и мне практика, и ему полезный фидбэк.

Я открыл браузер, потом терминал — и за пару дней ленивого ресерча собрал достаточно, чтобы написать эту статью.

Я пропентестил чужой пет-проект — и вот что нашел

Пентест чужого проекта без явного согласия — это уже не пентест, а взлом, даже если намерения добрые. Здесь было устное «да, смотри». Для серьезных проектов это оформляется письменно.

И сразу хорошая новость: в коде особых дыр нет. Всё найденное — это 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. Найденные уязвимости в сервисе уже исправлены, поэтому разбор безопасен.

Комментарии0
Тоже интересно
Комментировать
Поделиться
Скопировать ссылку
Telegram
WhatsApp
Vkontakte
Одноклассники