
Проверили изнутри безопасность этого портфолио: чат, API сообщений, сервер и 15 демо-концептов. Нашли 8 проблем, включая внедрение скрипта на домене чата, и исправили все 8.
Обзор
Этот сайт — не только статичные страницы. На нём работает чат на VITON ID (Firebase Auth и Firestore), небольшой API на Node, который хранит сообщения из формы и отправляет письма, сервер nginx в Docker и копии 15 концепт-сайтов в разделе /demos/. 28 сентября 2026 года мы проверили всё это изнутри: код, правила Firestore, настройки сервера и контейнеров и npm-зависимости.
Для каждой части мы задавали вопрос злоумышленника: что с ней может сделать посторонний и чего это будет стоить владельцу? Находка засчитывалась, только если мы её воспроизвели, а каждое исправление перед выпуском перепроверяли на локальной копии рабочей конфигурации.
8
находок по безопасности, все исправлены
Источник: Ревью кода, 28 сент. 2026
1
уязвимость высокого риска — внедрение скрипта на домене чата, закрыта
Источник: Воспроизведено до и после исправления на локальной копии
119
демо-страниц открываются с новой политикой безопасности без нарушений
Источник: Проверка в headless Chrome, 28 сент. 2026
0
известных уязвимостей в npm-зависимостях сайта и API
Источник: npm audit, 28 сент. 2026
Подход
Мы прочитали каждый маршрут API, проверку токенов, правила Firestore вместе с их 39 тестами в эмуляторе, файлы nginx и Docker Compose и код всех 15 демо. Вместо атак на рабочий сервер мы подняли те же контейнеры локально (nginx и API с рабочими настройками) и пробовали каждую атаку там.
Самая серьёзная находка оказалась не на самом сайте, а в демо. Каталог Oriva вставлял параметр адреса ?cat= в страницу как HTML, поэтому специально собранная ссылка запускала скрипт на tarasovvitalii.com — на том же домене, где чат хранит сессию Firebase. Одного клика владельца хватило бы, чтобы отдать переписку. Остальные 14 демо только сравнивают параметры адреса с известными значениями.
Исправления: демо принимает только известные категории и сортировки; у /demos/ своя политика безопасности (CSP); Leaflet закреплён хэшами целостности; редирект /demos/<id> собирается по строгому шаблону; адреса, которые могли подсунуть поля в ссылки mailto:, отклоняются; общий дневной лимит защищает хранилище; API работает от непривилегированного пользователя на файловой системе только для чтения; версия nginx скрыта, а HSTS покрывает www.
Галерея
Результаты
8 находок, 8 исправлено: 1 высокого уровня риска, 1 среднего и 6 низкого. Код, который срабатывал в старом демо, в новом ничего не делает, а запрос, который раньше подставлял заголовок Set-Cookie, теперь получает обычный 404. Все 119 демо-страниц открываются с новой политикой без единого нарушения, тесты API проходят 48 из 48 вместе с проверками новых правил, а npm audit не находит известных уязвимостей в зависимостях сайта и API.
Проверено и в порядке: проверка токенов Firebase (только RS256, обязательный идентификатор ключа, проверка аудитории, издателя и срока), правила Firestore (никто не читает чужую переписку и не пишет от имени владельца), шаблоны писем (все значения экранированы, подмены заголовков нет), логи (без токенов, email и IP-адресов) и интерфейс чата (текст посетителя никогда не вставляется как HTML).
Аудит безопасности
Каждую находку ниже мы сначала воспроизвели, а после исправления перепроверили на локальной копии рабочих контейнеров. Порядок — по уровню риска.
-
Высокий
Внедрение скрипта в демо-концепт на домене чата
ИсправленоБыло
Каталог Oriva вставлял параметр адреса ?cat= в страницу как HTML. Собранная ссылка запускала скрипт на tarasovvitalii.com, где чат хранит сессию Firebase: одного клика владельца хватило бы, чтобы открыть переписку и аккаунт владельца.
Стало
Принимаются только известные категории и сортировки. Тот же код больше не срабатывает, а остальные 14 демо проверены на такую же ошибку.
-
Средний
Демо без политики безопасности, чужой скрипт без проверки целостности
ИсправленоБыло
52 демо-страницы грузили Leaflet с unpkg.com без хэша целостности, а /demos/ не отдавал Content-Security-Policy: взломанный CDN или любой внедрённый скрипт получили бы доступ ко всему на домене.
Стало
Leaflet закреплён хэшами SRI, у /demos/ своя политика: запросы только к самому сайту, скрипты только с сайта и закреплённого файла, без плагинов. 119 страниц, 0 нарушений.
-
Низкий
Подмена заголовков в редиректе сервера
ИсправленоБыло
Редирект с /demos/<id> на /demos/<id>/ возвращал раскодированный путь как есть, поэтому %0d%0a в ссылке дописывал в ответ лишний заголовок, например Set-Cookie.
Стало
Редирект принимает только буквы, цифры, дефис и подчёркивание и сам собирает адрес. Тот же запрос теперь получает обычный 404.
-
Низкий
Скрытые поля в ссылках «Ответить» у владельца
ИсправленоБыло
Адрес вида me@x.com?bcc=…&body=… проходил проверку, и ссылка «Ответить» во входящих и письмах владельца подставляла скрытую копию и текст. Кроме того, в имени могли быть символы смены направления текста, которые маскируют написанное.
Стало
Адреса с разделителями URL отклоняются, в ссылках mailto: адрес кодируется, символы смены направления удаляются. Все три случая покрыты новыми тестами API.
-
Низкий
Нет общего лимита на хранение сообщений
ИсправленоБыло
Лимиты считались только по IP-адресу. Меняя адреса, можно было бесконечно добавлять заявки из формы и записи ботов, пока не кончится диск.
Стало
Поверх лимитов по IP действует общий дневной лимит на сохранение заявок (по умолчанию 500). Сверх него форма отвечает «попробуйте позже».
-
Низкий
API сообщений работал от root
ИсправленоБыло
Контейнер API работал от root, с записываемой файловой системой и всеми стандартными возможностями Linux: любая ошибка в нём дала бы злоумышленнику удобную точку опоры.
Стало
Он работает от непривилегированного пользователя node на файловой системе только для чтения, без возможностей Linux и без повышения привилегий.
-
Низкий
Версия сервера на виду, устаревшая ветка nginx
ИсправленоБыло
Каждый ответ содержал «server: nginx/1.27.5», а контейнер сайта использовал ветку 1.27, которая больше не получает исправлений.
Стало
Версия скрыта во всех ответах, а контейнер работает на актуальной стабильной ветке nginx.
-
Низкий
Принудительный HTTPS не охватывал www
ИсправленоБыло
Заголовок HSTS, который велит браузеру открывать сайт только по HTTPS, отдавался на основном домене, но не на www и не распространялся на поддомены.
Стало
www тоже отдаёт HSTS, а политика включает поддомены, так что http://www… тоже переводится на HTTPS.
Чего это не доказывает
Это наш собственный сайт, а не проект для клиента. Проверку провели мы сами по исходному коду; это не независимый пентест и не сертификат. Атаки мы воспроизводили на локальной копии рабочих контейнеров, а не на живом сервере.
Результат относится к тому, что мы проверили 28 сентября 2026 года: новый код, новые зависимости или новые демо требуют такой же проверки. Демо по-прежнему живут на домене сайта. Новая политика ограничивает, до чего дотянется внедрённый код, а полностью изолировать демо можно, только перенеся их на отдельный домен.
Задумали что-то похожее? Каждое сообщение лично читает наш основатель — Тарасов Виталий.
Заказать похожий проект на VITON13 Studio
Следующий проект
Möbius School & InstituteСайт школы и вуза с личным кабинетом
Концепция2026
Все работы (32)
