В начале года я решила провести эксперимент: закрыть все типичные уязвимости на сайте одного из клиентов и посмотреть, что останется. WAF, rate limiting, CSP, HttpOnly cookie,prepared statements — всё по учебнику. Через месяц сайт взломали через уязвимость, которую я даже не рассматривала как угрозу.

Что я сделала

Первым делом я просканировала сайт через OWASP ZAP. Нашла 14 уязвимостей, из них 3 критических: SQL-инъекция в форме обратной связи, хранимый XSS в комментариях и отсутствие защиты от CSRF. Исправила всё за два дня. Параллельно настроила Cloudflare с правилами firewall и включила логирование всех запросов к API.

Клиент был доволен. Отчёт показал: все критические уязвимости устранены, сайт соответствует требованиям безопасности. Я даже написала автоматические тесты, которые проверяли бы эти паттерны в будущем.

Как меня взломали

Злоумышленник не стал возиться с SQL-инъекцией — она действительно была закрыта. Вместо этого он нашёл уязвимость в стороннем JavaScript-виджете, который я не контролировала. Виджет загружался с внешнего CDN и имел доступ к DOM страницы. Через него XSS-атака сработала, хотя CSP был настроен строго.

Проблема была в том, что я защищала свой код, но не код третьих сторон. Атрибут integrity у CDN-скриптов не был проставлен, поэтому подмена содержимого виджета осталась незамеченной.

Что я поняла

Безопасность — это не чек-лист, который можно закрыть раз и навсегда. Это процесс. Я потратила две недели на защиту того, что могла контролировать, и упустила то, что не могла. После этого случая я добавила в свой чек-лист обязательный аудит всех внешних скриптов и политику использования только тех CDN, которые поддерживают Subresource Integrity.

Главный вывод: даже если ваш код идеален, уязвимость в зависимостях делает всю защиту бессмысленной. Проверяйте не только своё, но и всё, что загружаете извне.