Короткий ответ
Аудит безопасности сайта методом белого ящика читает исходный код и конфигурацию, а не гадает снаружи. На tarasovvitalii.com 28 сентября 2026 года он выявил восемь проблем: одну высокого риска (внедрение скрипта через демо на домене чата), одну среднего (у демо не было политики безопасности) и шесть низкого. Все восемь исправлены и перепроверены на копии рабочих контейнеров.
Зачем сайту-портфолио проверка безопасности
Портфолио кажется самым безопасным видом сайта: несколько страниц с работами и форма обратной связи. Но tarasovvitalii.com, сайт основателя VITON13, устроен сложнее. На нём работает чат на VITON ID, построенный на Firebase Authentication и Firestore, небольшой API на Node, который хранит сообщения из формы и отправляет письма, сервер nginx в Docker и копии пятнадцати концепт-сайтов в разделе /demos/. Посетитель, который входит, чтобы написать сообщение, доверяет всем этим частям одновременно.
28 сентября 2026 года VITON13 Studio проверила сайт так же, как проверила бы сайт клиента перед запуском. Для каждой части вопрос ставился с позиции злоумышленника: что с ней может сделать посторонний и во что это обойдётся владельцу? Находка засчитывалась, только когда её удавалось воспроизвести, а исправление — только когда та же атака против него не срабатывала.
Метод: сначала исходный код, атаки — на локальной копии
Аудит безопасности сайта методом белого ящика начинается изнутри. Проверяющие прочитали каждый маршрут API и код, который проверяет токены входа, правила Firestore с их 39 тестами в эмуляторе, конфигурацию nginx и Docker Compose, npm-зависимости сайта и API, интерфейс чата и код всех пятнадцати демо.
Вместо того чтобы прощупывать рабочий сервер, студия подняла те же контейнеры локально — nginx и API с боевыми настройками — и пробовала каждую атаку там. Так живой сайт и его посетители остаются вне эксперимента, а один и тот же запрос можно повторить до и после исправления, чтобы доказать, что изменение работает. На живом адресе перепроверялся только итог: заголовки и редиректы.
Затем каждой подтверждённой находке присваивался уровень риска. Высокий означал, что посторонний может действовать внутри чужой сессии или добраться до закрытых данных; средний — что не хватает слоя защиты, из-за чего мелкая ошибка превратилась бы в крупную; низкий — слабость, которой нужны необычные условия или через которую утекает немного. Список ниже идёт в этом порядке, и для каждого пункта записано, что делала атака до исправления и что делает тот же запрос после него.
Находка высокого риска: внедрение скрипта через демо
Самая серьёзная проблема нашлась не в самом портфолио, а в одном из его демо. Концепт магазина Oriva читал параметр ?cat= из адреса и вставлял его в страницу как HTML. Значит, специально собранная ссылка могла запустить скрипт на tarasovvitalii.com. OWASP описывает этот класс ошибок — межсайтовый скриптинг — как внедрение скрипта в сайт, которому жертва доверяет, и именно это делало ошибку здесь опасной.
Демо живут на том же домене, что и чат, а чат хранит сессию Firebase в браузере на этом домене. Одного клика владельца сайта по такой ссылке могло хватить, чтобы открыть доступ к входящим сообщениям и аккаунту владельца. Когда проблема найдена, исправление простое: теперь демо принимает только известные значения категории и сортировки и игнорирует всё остальное. Остальные четырнадцать демо проверили на тот же шаблон — они лишь сравнивают параметры адреса с фиксированными значениями.
Политика безопасности и закреплённые скрипты для демо
Находка среднего риска касалась эшелонированной защиты. Раздел /demos/ не отдавал заголовок Content-Security-Policy, который сообщает браузеру, из каких источников странице разрешено загружать скрипты, стили и данные. Вдобавок 52 демо-страницы подключали Leaflet, библиотеку карт, с публичного CDN без хэша целостности. Если бы этот CDN когда-нибудь взломали или если бы какой-то скрипт внедрили в страницу, ничто не ограничило бы, до чего он может дотянуться.
Теперь у /demos/ своя политика: запросы только к самому сайту, скрипты только с сайта и из закреплённой библиотеки, никаких плагинов. Leaflet закреплён хэшами Subresource Integrity, поэтому браузер отвергнет файл, если в нём изменится хотя бы один байт. В headless Chrome все 119 демо-страниц загрузились с новой политикой без единого нарушения.
Подмена заголовков в серверном редиректе
Правило nginx, которое перенаправляло /demos/<id> на /demos/<id>/, подставляло декодированный путь обратно в заголовок Location. Поэтому ссылка с %0d%0a — закодированным переводом строки — могла добавить в ответ собственный заголовок, например Set-Cookie. OWASP относит это к CRLF-инъекциям: символы возврата каретки и перевода строки протаскиваются туда, где сервер собирает заголовки.
Теперь редирект принимает только буквы, цифры, дефисы и подчёркивания и сам собирает целевой адрес. Тот же запрос, который раньше возвращал 301 с подставленной cookie, теперь получает обычный 404 — и на локальной копии, и на живом сайте.
Пять мелких правок: почта, лимиты и сервер
Три находки низкого риска касались системы сообщений. Адрес вида me@x.com?bcc=…&body=… проходил проверку, поэтому ссылка «Ответить» во входящих владельца могла открыться со скрытым получателем копии и заранее вписанным текстом; такие адреса теперь отклоняются, а каждый адрес в ссылке mailto: кодируется. В именах могли встречаться символы смены направления текста, которые маскируют написанное; теперь они удаляются. Лимиты действовали только на один IP-адрес, поэтому, меняя адреса, можно было заполнить диск; теперь поверх них стоит общий дневной предел в 500 сохранённых заявок.
Остальное касалось настроек сервера. API сообщений работал от root с доступной для записи файловой системой; теперь он запускается от непривилегированного пользователя на файловой системе только для чтения, и все возможности Linux отключены. Каждый ответ показывал точную версию nginx из ветки, которая больше не получает исправлений; версия теперь скрыта, а контейнер работает на текущей стабильной ветке. HSTS — заголовок, который удерживает браузеры на HTTPS, — отдавался на основном домене, но не на www; теперь он охватывает www и поддомены.
Части сайта, прошедшие проверку без изменений
Аудит, который перечисляет только проблемы, скрывает половину своей пользы. Проверка подтвердила и то, что уже было сделано хорошо: код верификации токенов входа принимает только RS256, требует идентификатор ключа и сверяет аудиторию, издателя и срок действия; правила Firestore не позволяют никому читать чужую переписку или писать от имени владельца; шаблоны писем экранируют каждое значение; в логах нет токенов, адресов почты и IP-адресов; а чат никогда не вставляет текст посетителя как HTML.
После исправлений набор тестов API проходит 48 из 48 с учётом новых правил, а npm audit не сообщает об известных уязвимостях в зависимостях сайта и API.
В тот же день на живом адресе заголовки ответов проверили ещё раз: версии сервера нет ни в одном ответе, HSTS с поддоменами стоит и на домене без www, и на www, политика демо действует на /demos/, а на запрос с подменой заголовка приходит обычный 404. Каталог Oriva на живом сайте теперь сверяет категорию со своим фиксированным списком, прежде чем её использовать, а библиотека карт загружается с хэшами целостности.
Пределы аудита, который студия провела на своём сайте
Это собственный сайт студии, и проверяла его сама студия. Такая проверка — не независимый пентест и не сертификат, и она относится к коду по состоянию на 28 сентября 2026 года. Новый код, новые зависимости или новое демо требуют такой же проверки заново, поэтому контроль безопасности должен быть частью процесса выпуска, а не разовым проектом.
Один структурный риск сохранён сознательно: демо по-прежнему делят домен с сайтом. Новая политика ограничивает, до чего может дотянуться внедрённый код, но полностью изолировать демо можно только переносом на отдельный домен. Для малого бизнеса вывод практичный: самая опасная строка кода часто оказывается на забытой дополнительной странице, а не в форме входа.
Практический чеклист
- Перечислите все части сайта, где выполняется код: формы, чаты, API, админ-страницы и старые демо.
- Проверьте, что каждая страница со входом или сессией отдаёт Content-Security-Policy.
- Закрепите каждый скрипт с CDN хэшем целостности или разместите его у себя.
- Поищите в коде параметры адреса, которые попадают в страницу как HTML.
- Убедитесь, что HSTS отдаётся и на домене без www, и на www, и скройте версию сервера.
Вопросы и ответы
Что такое аудит безопасности сайта методом белого ящика?
Это проверка с доступом к исходному коду и конфигурации сервера. Вместо догадок снаружи проверяющий читает код, находит вероятные слабые места и затем воспроизводит их на копии реальной инфраструктуры.
Почему самой рискованной частью сайта оказалось демо-концепт?
Демо работают на том же домене, что и чат, который хранит там свою сессию входа. Поэтому скрипт, внедрённый через демо, мог действовать внутри сессии владельца, хотя само демо никаких данных не хранит.
Заменяет ли Content-Security-Policy исправление ошибки?
Нет. Политика ограничивает, что внедрённый код может загрузить или достать, и тем самым уменьшает ущерб. Саму уязвимость всё равно нужно устранить — здесь это сделали, разрешив только известные значения.
Этот аудит — пентест или сертификат?
Ни то, ни другое. Это собственная проверка студией своего сайта по исходному коду, с атаками, воспроизведёнными на локальной копии рабочих контейнеров. Независимое тестирование было бы отдельной работой.
Как часто небольшому сайту стоит повторять такую проверку?
Всякий раз, когда заметно меняются код, зависимости или сторонние скрипты, и как минимум перед каждым крупным релизом. Запуск npm audit и проверка заголовков при каждом деплое частично закрывают это автоматически.
