Безопасность сайта: как защитить сайт на каждом уровне (2026)
Ключевой вывод
Безопасность сайта строится уровнями: закрепите за собой домен и DNS (registrar lock, DNSSEC, CAA), включите современный TLS с HSTS, отправляйте строгий набор HTTP-заголовков безопасности, хешируйте пароли с Argon2id и прикройте вход MFA и rate limiting, проверяйте входные данные и права доступа на сервере, фиксируйте версии зависимостей и проводите их аудит, закройте все порты, которые вы не обслуживаете, и ведите журналы, по которым можно заметить взлом. Для каждой практики ниже приведены точная конфигурация и бесплатный способ убедиться, что она действительно работает: меры защиты, которую вы не проверили, у вас фактически нет.
Проверьте сейчас

Начать чтение
- Что на самом деле означает безопасность сайта
- Стек веб-безопасности: шесть уровней защиты
- Уровень 1: защитите домен и DNS
- Уровень 2: HTTPS и TLS как положено
- Уровень 3: HTTP-заголовки безопасности
- Уровень 4: аутентификация, устойчивая к credential stuffing
- Сессии, cookie и CSRF
- Инъекции и XSS: проверяйте ввод, кодируйте вывод
- Broken Access Control: риск номер один
- Уровень 5: зависимости и цепочка поставок ПО
- Уровень 6: защита сервера (порты, патчи, секреты, резервные копии)
- Аутентификация почты: SPF, DKIM и DMARC
- Логируйте, мониторьте и готовьтесь к взлому
- Проверяйте: сканеры, пентесты и непрерывный контроль
- Чек-лист безопасности сайта
- Как это соотносится с OWASP Top 10:2025
- Частые вопросы о безопасности сайта
Что на самом деле означает безопасность сайта
Безопасность сайта — это набор мер защиты, которые не дают взломать сайт или веб-приложение, подменить его содержимое, использовать его для атак на собственных посетителей или незаметно выкачать из него данные. Это не один продукт и не одна настройка. Это цепочка решений, которая начинается у регистратора домена и проходит через DNS, TLS, HTTP-заголовки, которые отправляет ваш сервер, код, обрабатывающий вход в систему и пользовательский ввод, сторонние пакеты, на которых построено приложение, сам сервер и, наконец, журналы, из которых вы узнаёте, что что-то пошло не так.
Причина мыслить уровнями в том, что именно так мыслят атакующие. В отчёте Verizon 2026 Data Breach Investigations Report, охватившем более 22 000 утечек с ноября 2024 по октябрь 2025 года, сказано: 31% утечек начались с эксплуатации уязвимости в ПО, тогда как годом ранее было 20%, и впервые это больше, чем украденные учётные данные (13%); доля фишинга осталась на уровне 16%. Программы-вымогатели участвовали в 48% утечек против 44%. Достаточно одного непропатченного обновления, утёкшего API-ключа или админки без rate limiting. Ничего экзотического в этом руководстве нет: взламывают, как правило, те сайты, где на одном уровне пропустили базовые меры, пока шлифовали другой.
Руководство построено снаружи внутрь. Для каждой меры указано зачем она нужна, что именно настроить и как это проверить командой или бесплатным инструментом, например проверкой HTTP-заголовков или проверкой SSL-сертификата: настройка, которая, как вам кажется, включена, но ни разу не проверена, ничего не защищает.
Проверьте заголовки безопасности сайта
Стек веб-безопасности: шесть уровней защиты
Любая атака на сайт приходится на один из шести уровней. Таблица ниже — карта для всего остального руководства: что находится на каждом уровне, как его обычно атакуют и какая бесплатная проверка подтвердит, что ваша защита действительно работает.
| Уровень | Что делают атакующие | Основные практики | Как проверить |
|---|---|---|---|
| 1. Домен и DNS | Угоняют домен, меняют NS-серверы, захватывают «висячие» поддомены, получают поддельные сертификаты | Registrar lock, MFA, DNSSEC, CAA, аудит устаревших записей | WHOIS-поиск, Проверка DNS домена, Поиск поддоменов |
| 2. Транспорт (TLS) | Понижают соединение до HTTP, снимают шифрование, эксплуатируют устаревшие шифры и просроченные сертификаты | TLS 1.2+, HSTS (preload по желанию), автоматическое продление | Проверка SSL-сертификата |
| 3. HTTP-заголовки | Внедряют скрипты (XSS), встраивают сайт во фрейм для кликджекинга, пользуются угадыванием типа содержимого, перехватывают утечки через Referer | CSP, frame-ancestors, nosniff, Referrer-Policy, Permissions-Policy | Проверка HTTP-заголовков |
| 4. Приложение | Credential stuffing, инъекции, нарушение контроля доступа, CSRF, небезопасная загрузка файлов | Argon2id + MFA + rate limiting, параметризованные запросы, авторизация на сервере, cookie с SameSite | Код-ревью, ZAP, Тест надёжности пароля |
| 5. Зависимости | Отравленные пакеты, известные CVE в библиотеках, скомпрометированные CI-пайплайны | Lockfile, аудит, фиксированные версии, SBOM, токены с минимальными правами | npm audit, Dependabot, Trivy |
| 6. Сервер и эксплуатация | Открытые порты, учётные данные по умолчанию, отсутствующие патчи, нет резервных копий, нет журналов | Файрвол, SSH только по ключам, патчи, резервное копирование 3-2-1, централизованное логирование | Проверка портов, Проверка IP в чёрных списках |
Двигайтесь сверху вниз. Уровни 1–3 — это конфигурация, которую можно закончить за полдня, и она защищает сразу все страницы. Уровни 4–6 — постоянные привычки, которые должны стать частью код-ревью и процесса деплоя.
Уровень 1: защитите домен и DNS
Всё остальное в этом руководстве предполагает, что домен по-прежнему под вашим контролем. Если атакующий может войти в ваш аккаунт у регистратора или сменить NS-серверы, он направит ваш трафик куда угодно, получит действительный сертификат на ваше имя и будет читать всю адресованную вам почту — а ни одна строка кода вашего приложения даже не выполнится. Поэтому защита домена и DNS — первая из лучших практик веб-безопасности, и почти целиком это вопрос конфигурации.
Начните с WHOIS-поиска по собственному домену и убедитесь в трёх вещах: среди статусов есть clientTransferProhibited, до окончания регистрации больше года, а регистратор — тот, у которого у вас действительно есть аккаунт. Удивительно часто домен компании числится в аккаунте бывшего подрядчика. В нашем руководстве по WHOIS-поиску разобраны все коды статусов, которые вы увидите.
Registrar lock, MFA и напоминания об окончании срока
Включите registrar lock (его также называют блокировкой трансфера), чтобы домен нельзя было перенести к другому регистратору, пока вы явно не снимете блокировку, и включите многофакторную аутентификацию в самом аккаунте регистратора. Аккаунты у регистраторов — излюбленная цель фишинга именно потому, что один вход открывает доступ ко всему, что от него зависит. Если регистратор позволяет, используйте аппаратный ключ или приложение-аутентификатор, а не SMS.
Включите автопродление домена с платёжным средством, срок действия которого не истечёт, и всё равно поставьте напоминание в календаре за 60 дней до окончания регистрации. Просроченный домен пропадает не в день окончания, но когда льготный период и период выкупа заканчиваются (для .com это примерно до 80 дней), домен удаляется, а сервисы drop-catch регистрируют его в течение нескольких секунд после освобождения. Покупатель получает вашу почту, ваш трафик и все OAuth-входы, которые доверяют вашему домену.
- Registry lock (ручная блокировка на уровне реестра, снимаемая только через отдельный канал) стоит дополнительных денег для домена, от которого зависит бизнес: он не даёт сменить NS-серверы даже при взломанном аккаунте у регистратора.
- Разделите роли. Кто платит за домен, какой аккаунт им владеет и кто предоставляет DNS — всё это должно быть задокументировано и не привязано к почтовому ящику одного сотрудника.
- Включите защиту данных в WHOIS, чтобы ваши контакты не стали картой для фишинга, и укажите в аккаунте ролевой адрес, который кто-то регулярно читает, например
domains@.
Подпишите зону с помощью DNSSEC
DNSSEC подписывает DNS-зону, чтобы резолвер мог обнаружить поддельные ответы. Без него атакующий, который может отравить кэш резолвера, отправит ваших посетителей на поддельный сервер, а в браузере по-прежнему будет виден ваш домен. Большинство управляемых DNS-провайдеров (Cloudflare, Route 53, Google Cloud DNS и многие регистраторы) включают его одним переключателем; единственный ручной шаг — опубликовать DS-запись у регистратора. Проверьте через dig или с помощью проверки DNS домена, которая проверяет DS-запись и подписи:
dig +dnssec +short yourdomain.com A
# A signed zone returns the record AND an RRSIG line:
# 203.0.113.10
# A 13 2 3600 20270115000000 20261225000000 34505 yourdomain.com. Kx3f...==
dig +short yourdomain.com DS
# A DS record at the parent zone proves the chain of trust is complete
# 2371 13 2 6A1B...C9Ограничьте выпуск сертификатов записью CAA
Запись CAA (RFC 8659) сообщает центрам сертификации, кому из них разрешено выпускать сертификаты для вашего домена. Каждый публичный центр сертификации обязан проверять её перед выпуском, поэтому запись CAA закрывает дверь атакующему, который сумел обмануть проверку домена в каком-то другом центре. Три записи покрывают типичные случаи: кто может выпускать сертификаты, кто может выпускать wildcard-сертификаты и куда сообщать об отклонённом запросе:
yourdomain.com. CAA 0 issue "letsencrypt.org"
yourdomain.com. CAA 0 issuewild ";"
yourdomain.com. CAA 0 iodef "mailto:security@yourdomain.com"Аудит устаревших записей: subdomain takeover
Захват поддомена происходит, когда DNS-запись всё ещё указывает на сервис, которым вы перестали пользоваться: CNAME на удалённый сайт GitHub Pages, приложение Azure, бакет S3 или имя хоста стороннего SaaS. Любой может зарегистрировать брошенную цель и затем отдавать контент на old-app.yourdomain.com от вашего имени, причём с действительным сертификатом. Это регулярная находка в программах bug bounty, потому что никто не помнит о существовании записи.
Прогоните домен через Поиск поддоменов: он ищет в двух сервисах журналов Certificate Transparency, Cert Spotter (действующие сертификаты) и crt.sh (все сертификаты с 2013 года), перебирает 1000 распространённых имён, разрешает каждое имя вживую, помечает «висячие» CNAME и показывает, какой хост или CDN обслуживает каждое имя. Удалите каждую запись, цель которой возвращает NXDOMAIN или страницу провайдера «такого приложения нет»; одно конкретное имя подтвердит DNS-поиск. Повторяйте раз в квартал и включите удаление DNS-записи в процедуру вывода любого сервиса из эксплуатации.
Certificate Transparency — это ещё и система раннего предупреждения: Cert Spotter и CT-мониторинг Cloudflare могут присылать письмо каждый раз, когда любой центр сертификации выпускает сертификат для вашего домена. Неожиданный сертификат часто становится первым видимым признаком компрометации DNS или аккаунта у регистратора.
Уровень 2: HTTPS и TLS как положено
HTTPS давно не «лучшая практика», а базовый минимум: браузеры помечают обычные HTTP-страницы как «Не защищено». Кроме того, Google объявил, что начиная с Chrome 154 (октябрь 2026 года) браузер будет спрашивать подтверждение перед открытием любого публичного сайта по обычному HTTP (Google). То, что по-прежнему отличает защищённый сайт от просто зашифрованного, — это принимаемые версии TLS, возможность вообще обратиться к вам по HTTP и достаточная автоматизация продления, чтобы пережить сокращение срока действия сертификатов, начавшееся в марте 2026 года.
Минимум TLS 1.2, предпочтительно TLS 1.3
TLS 1.0 и 1.1 официально признаны устаревшими в RFC 8996 в 2021 году, и ни один современный браузер их не согласует. Отключите их на сервере, отдавайте предпочтение TLS 1.3 (в нём нет ни одного известного слабого шифра, а рукопожатие занимает один круговой обмен) и оставляйте TLS 1.2 только с AEAD-наборами шифров, например ECDHE-ECDSA-AES128-GCM-SHA256 или ECDHE-RSA-CHACHA20-POLY1305.
Когда проверяете снаружи, важны две строки: согласованный протокол и Verify return code: 0, означающий, что вся цепочка сертификатов прошла проверку. Отсутствующий промежуточный сертификат — частая причина жалоб вида «в Chrome работает, а в curl и на Android нет»; как это исправить, показывает наше руководство по цепочке SSL-сертификатов, а проверка SSL-сертификата напрямую сообщает о неполной цепочке. Вот проверка dnsrobot.net на 27 октября 2026 года, а затем тест, что сервер отклоняет TLS 1.1:
echo | openssl s_client -connect dnsrobot.net:443 -servername dnsrobot.net 2>/dev/null \
| grep -E 'Protocol|Cipher|Verify return'
# New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
# Protocol: TLSv1.3
# Verify return code: 0 (ok)
# Is TLS 1.1 refused? SECLEVEL=0 lets OpenSSL 3 and later offer it at all
openssl s_client -connect example.com:443 -servername example.com -tls1_1 \
-cipher 'DEFAULT:@SECLEVEL=0' </dev/null 2>&1 | grep -E 'alert|Cipher is'
# Refused, as it should be: ...tlsv1 alert protocol version...
# New, (NONE), Cipher is (NONE)
# Still accepted (fix it): New, TLSv1.0, Cipher is ECDHE-ECDSA-AES128-SHA
# (TLSv1.0 is the cipher's version; the handshake was TLS 1.1)Списки шифров — место, где скопированные конфигурации устаревают. Вместо того чтобы писать их вручную, сгенерируйте актуальный рекомендуемый блок для nginx, Apache, Caddy или HAProxy в Mozilla SSL Configuration Generator и выбирайте профиль «Intermediate», если только вам не нужна поддержка очень старых клиентов.
HSTS: запретите браузеру возвращаться к HTTP
Одного редиректа с HTTP на HTTPS недостаточно. Самый первый запрос посетителя к http://yourdomain.com всё равно уходит открытым текстом, и атакующий в той же сети может ответить на него раньше, чем сработает ваш редирект. HTTP Strict Transport Security (RFC 6797) решает эту проблему: увидев заголовок один раз, браузер переписывает каждый последующий URL http:// для вашего домена на https:// ещё до отправки запроса.
Задайте max-age не меньше одного года (31536000 секунд) и добавьте includeSubDomains, когда все поддомены будут работать по HTTPS. Добавление домена в preload-список на hstspreload.org, который зашивает домен в Chrome, Firefox, Safari и Edge, необязательно: сами авторы списка теперь пишут, что HSTS рекомендуется, а preload нет, потому что Chrome и Safari и так переводят переходы на HTTPS, а выйти из списка получается лишь за месяцы. Проверьте заголовок в проверке HTTP-заголовков, которая оценивает HSTS тестами Mozilla HTTP Observatory.
Strict-Transport-Security: max-age=31536000; includeSubDomainsСертификаты на 47 дней: автоматизируйте продление уже сейчас
Голосование CA/Browser Forum SC-081 поэтапно сокращает максимальный срок действия всех публичных TLS-сертификатов. Первое сокращение вступило в силу 15 марта 2026 года, так что любой сертификат, который вы покупаете или продлеваете сегодня, уже ограничен 200 днями, а к 2029 году сертификат придётся менять примерно каждые шесть недель:
| Дата вступления в силу | Максимальный срок действия сертификата | Повторное использование проверки домена |
|---|---|---|
| До 15 марта 2026 года | 398 дней | 398 дней |
| 15 марта 2026 года | 200 дней | 200 дней |
| 15 марта 2027 года | 100 дней | 100 дней |
| 15 марта 2029 года | 47 дней | 10 дней |
Ручное продление такой график не переживёт. Лучшая практика — полностью автоматический выпуск через ACME: Let's Encrypt или Google Trust Services через certbot, acme.sh, Caddy или встроенные автоматические сертификаты Cloudflare, Vercel, Netlify и большинства панелей хостинга. Проверьте продление заранее, до того как оно понадобится (certbot renew --dry-run для certbot); ищите прежде всего запись CAA или правило файрвола, добавленные после прошлого продления и теперь блокирующие проверку домена. Что бы вы ни использовали, следите за сроком действия и снаружи: проверка SSL-сертификата показывает, сколько дней осталось, а просроченный сертификат превращает каждый визит в полноэкранное предупреждение браузера.
Уровень 3: HTTP-заголовки безопасности
Заголовки безопасности — это инструкции, которые сервер даёт браузеру о том, что можно и чего нельзя делать с вашими страницами: какие скрипты могут выполняться, можно ли встраивать страницу во фрейм, может ли браузер угадывать тип содержимого и что сообщать другим сайтам о том, откуда пришёл посетитель. Они ничего не стоят, применяются сразу ко всем страницам и блокируют целые классы атак. Кроме того, именно на этом уровне большинство сайтов хотя бы частично ошибается, поэтому проверка HTTP-заголовков оценивает их тестами Mozilla HTTP Observatory. Вот что отправлял github.com 27 октября 2026 года, урезано до заголовков безопасности (в его script-src указаны один хост с ресурсами и хеш скрипта, без 'unsafe-inline'):
curl -sI https://github.com/ | grep -iE 'strict-transport|content-security|x-frame|x-content-type|referrer-policy'
strict-transport-security: max-age=31536000; includeSubdomains; preload
x-frame-options: deny
x-content-type-options: nosniff
referrer-policy: origin-when-cross-origin, strict-origin-when-cross-origin
content-security-policy: default-src 'none'; base-uri 'self'; ... frame-ancestors 'none'; ... script-src github.githubassets.com 'sha256-6AKuI+cDd1MoUL1u4eqrA9PXmnPAkOB4h7421hPUUm0='; ...Семь заголовков, которые должен отправлять каждый сайт
Вот значения, к которым стоит стремиться на обычном сайте. В оценке Mozilla HTTP Observatory, которую использует наша проверка HTTP-заголовков, отсутствие CSP стоит 25 баллов, отсутствие HSTS или защиты от встраивания во фрейм по 20, а отсутствие nosniff 5; Referrer-Policy и COOP добавляют бонусные баллы, если счёт всё ещё 90 или выше. Permissions-Policy в оценке не участвует (его читают только браузеры на Chromium), но задать его всё равно стоит.
| Заголовок | Рекомендуемое значение | От чего защищает |
|---|---|---|
Strict-Transport-Security | max-age=31536000; includeSubDomains | Понижение протокола, кража cookie по HTTP |
Content-Security-Policy | script-src на основе nonce с 'strict-dynamic'; object-src 'none'; base-uri 'none'; frame-ancestors 'none' | Межсайтовый скриптинг (XSS), внедрение данных, кликджекинг |
X-Content-Type-Options | nosniff | MIME-сниффинг, превращающий загруженный файл в исполняемый скрипт |
Referrer-Policy | strict-origin-when-cross-origin | Утечка полных URL (токенов, поисковых запросов) третьим сторонам |
Permissions-Policy | camera=(), microphone=(), geolocation=(), payment=() | Скрытое использование мощных API браузера сторонними скриптами |
X-Frame-Options | DENY (устаревший запасной вариант для frame-ancestors) | Кликджекинг в браузерах без поддержки CSP Level 2 |
Cross-Origin-Opener-Policy | same-origin | Межоконные атаки через window.opener |
Content Security Policy, которая не ломает сайт
Content Security Policy — самая эффективная защита от XSS: она не даёт внедрённым скриптам выполниться, даже если уязвимость для инъекции существует. Подвох в том, что строгая политика ломает любой встроенный скрипт или сторонний тег, о котором вы забыли. Современный подход, который рекомендует Google и который описан на MDN, — политика на основе nonce: сервер генерирует случайный nonce для каждого ответа, добавляет его в каждый тег script, который выводит намеренно, а всё остальное браузер выполнять отказывается.
'strict-dynamic' делает такую политику практичной: скрипт с nonce может подгружать другие скрипты (аналитику, менеджеры тегов, виджеты), и не нужно вносить каждый из них в белый список, а токены https: и 'unsafe-inline' современные браузеры игнорируют — они служат лишь запасным вариантом для старых. Начните с Content-Security-Policy-Report-Only, неделю понаблюдайте за отчётами о нарушениях, затем переключитесь в режим блокировки. У Next.js, Rails и Django есть встроенная поддержка nonce; статические сайты могут вместо этого использовать script-src на основе хешей.
Content-Security-Policy:
script-src 'nonce-r4nd0m1z3dV4lu3' 'strict-dynamic' https: 'unsafe-inline';
object-src 'none';
base-uri 'none';
frame-ancestors 'none';
form-action 'self';
report-to csp-endpoint
<!-- Only scripts carrying the nonce run; strict-dynamic lets them load their own dependencies -->
<script nonce="r4nd0m1z3dV4lu3" src="/app.js"></script>Политика на основе белого списка, например script-src 'self' https://cdn.example.com, лучше, чем ничего, но любой JSONP-эндпоинт или старая библиотека на разрешённом хосте позволит её обойти. Если вы пока не можете использовать nonce, задайте хотя бы object-src 'none' и base-uri 'none' — это сразу закрывает два распространённых способа обхода.
Кликджекинг: frame-ancestors и X-Frame-Options
При кликджекинге ваш сайт невидимо загружается внутри страницы атакующего, и посетителя вынуждают нажать кнопку, которую он не видит. frame-ancestors 'none' в CSP (или 'self', если вы встраиваете собственные страницы) предотвращает это во всех современных браузерах, а X-Frame-Options: DENY защищает старые. Если вы сознательно предлагаете встраиваемый виджет, сделайте исключение только для этого пути и только для тех источников, которым оно нужно; в нашем руководстве по X-Frame-Options разобраны точные серверные правила и ошибки «refused to connect», с которыми вы столкнётесь при тестировании.
nosniff, Referrer-Policy, Permissions-Policy и COOP
X-Content-Type-Options: nosniff не даёт браузеру переопределять Content-Type на свой лад — а именно так загруженное пользователем «изображение» с HTML внутри превращается в исполняемую страницу. Referrer-Policy: strict-origin-when-cross-origin (значение по умолчанию в современных браузерах, но задайте его явно) передаёт другим сайтам только ваш источник (origin), поэтому токены сброса пароля и поисковые запросы в URL остаются приватными. Permissions-Policy отключает функции браузера, которые вы не используете, чтобы скомпрометированный сторонний скрипт не мог включить камеру или прочитать геолокацию. Cross-Origin-Opener-Policy: same-origin разрывает связь window.opener со страницами, которые вы открываете, и блокирует целый класс межоконных атак.
Удалять заголовки тоже важно: Server и X-Powered-By сообщают сканерам уязвимостей точные версии ПО, а X-XSS-Protection устарел и в старых браузерах может порождать ошибки, поэтому задайте ему 0 или уберите. Определение CMS показывает, что любой может узнать о вашем стеке и его версиях прямо со страницы.
Задавайте заголовки один раз на периметре (CDN, обратный прокси или middleware фреймворка), а не для каждой страницы отдельно. В Next.js это файл proxy или middleware; в nginx — блок add_header в контексте server; в Apache — Header always set. Затем после каждого деплоя заново запускайте проверку HTTP-заголовков: новая версия фреймворка или настройка CDN может незаметно убрать один из них.
Уровень 4: аутентификация, устойчивая к credential stuffing
Атакующие редко подбирают пароли по одному. Они прогоняют миллиарды пар «email — пароль», утёкших с других сайтов (credential stuffing), а остальное добывают фишингом. Поэтому лучшая практика аутентификации состоит из трёх частей: хранить пароли так, чтобы утечка базы данных не стала утечкой паролей, сделать украденный пароль бесполезным самим по себе и сделать перебор слишком медленным, чтобы он имел смысл. В OWASP Top 10:2025 категория Authentication Failures (ошибки аутентификации) занимает позицию A07.
Хешируйте пароли с Argon2id или bcrypt
Никогда не храните пароли в открытом виде и никогда не храните их быстрые хеши. MD5, SHA-1 и даже SHA-256 спроектированы быстрыми, поэтому утёкшую таблицу таких хешей можно перебирать со скоростью миллиардов попыток в секунду на одной видеокарте. Используйте медленную функцию хеширования паролей с высокими требованиями к памяти и уникальную соль для каждого пароля. OWASP Password Storage Cheat Sheet рекомендует в таком порядке:
- Argon2id минимум с 19 МиБ памяти, 2 итерациями и степенью параллелизма 1 (или 46 МиБ с 1 итерацией).
- scrypt с N = 2^17, r = 8, p = 1, если Argon2 недоступен.
- bcrypt с коэффициентом сложности (work factor) 10 или выше (учитывайте ограничение на входные данные в 72 байта: предварительное хеширование более длинных паролей требует аккуратности).
- PBKDF2-HMAC-SHA256 с 600 000 итераций — только если этого требует соответствие FIPS.
// Node.js with the argon2 package
import argon2 from "argon2";
const hash = await argon2.hash(password, {
type: argon2.argon2id,
memoryCost: 19456, // KiB = 19 MiB
timeCost: 2,
parallelism: 1,
});
// Later, on login:
const ok = await argon2.verify(hash, submittedPassword);Подберите стоимость так, чтобы один хеш считался примерно 100–250 мс на сервере входа. Реальный пользователь этого не заметит, но каждое ядро процессора будет перебирать лишь горстку вариантов в секунду, а даже мощная видеокарта — тысячи, а не миллиарды. Пересчитывайте хеш при успешном входе всякий раз, когда повышаете параметры, чтобы старые хеши обновлялись сами.
Правила для паролей, которые действительно помогают (NIST SP 800-63B)
Правила состава, которые большинство сайтов всё ещё навязывает (одна заглавная буква, один спецсимвол, смена каждые 90 дней), порождают предсказуемые пароли вроде Summer2026! и подталкивают людей использовать их повторно. Актуальные рекомендации NIST SP 800-63B заменяют их правилами, которые измеряют то, что действительно важно:
- Минимум 8 символов, если также требуется MFA, и не менее 15 символов для пароля, который используется без второго фактора; разрешайте длину не меньше 64 символов.
- Никаких правил состава и никакой принудительной периодической смены; требуйте смену пароля только при признаках компрометации.
- Проверяйте каждый новый пароль по базе утечек (range API сервиса Have I Been Pwned позволяет делать это, не передавая сам пароль) и отклоняйте пароли, которые уже утекли.
- Разрешайте вставку из буфера обмена и менеджеры паролей, принимайте пробелы и Unicode и давайте подсказки, которые помогают выбрать надёжный пароль, особенно сразу после того, как вы отклонили предыдущий.
Наш Тест надёжности пароля показывает, чем эти меры отличаются от старых правил: он оценивает, за какое время пароль взламывается при 8 именованных атаках, показывает шаблоны, которые его ослабляют, и проверяет пароль по базам утечек, не отправляя его, — это гораздо полезнее красного сообщения «добавьте спецсимвол».
MFA и passkeys
Многофакторная аутентификация превращает украденный пароль в тупик. Предлагайте её всем и делайте обязательной для администраторов, финансовых сотрудников и службы поддержки. Ранжируйте варианты по устойчивости к фишингу: passkeys и ключи безопасности FIDO2 невозможно выманить фишингом, потому что учётные данные привязаны к вашему настоящему источнику (origin); приложения-аутентификаторы (TOTP) — хороший вариант; SMS-коды лучше, чем ничего, но их можно перехватить через подмену SIM-карты (SIM swapping). Passkeys поддерживаются во всех современных браузерах и операционных системах и полностью избавляют от пароля, а значит, и от credential stuffing как класса атак.
Защищайте путь восстановления так же тщательно, как вход: храните коды восстановления в виде хешей, для сброса по email используйте одноразовые токены, которые истекают в течение 15 минут, и откажитесь от контрольных вопросов.
Rate limiting, блокировка и защита от ботов
Ограничивайте частоту запросов к эндпоинтам входа, регистрации, сброса пароля и MFA по IP-адресу и по учётной записи, а после достижения лимита возвращайте 429 Too Many Requests с заголовком Retry-After (в нашем руководстве по ошибке HTTP 429 описано, как должны реагировать клиенты). Дополните это нарастающей задержкой или временной блокировкой после повторных неудачных попыток входа в одну учётную запись, а для эндпоинтов, на которые идут распределённые атаки, — CAPTCHA или проверкой proof-of-work. Записывайте в журнал каждую неудачную попытку входа с IP-адресом источника, чтобы картина атаки credential stuffing была видна через минуты, а не через месяцы.
# nginx: 5 login attempts at once, then 1 every 12 seconds, per client IP
# (limit_req sends no Retry-After header; add one on your 429 page if clients need it)
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
location = /api/login {
limit_req zone=login burst=4 nodelay;
limit_req_status 429;
proxy_pass http://app;
}Показывайте одинаковое сообщение об ошибке для «неизвестного пользователя» и «неверного пароля» и добейтесь одинакового времени ответа в обоих случаях (хешируйте фиктивный пароль, если пользователя не существует). Иначе форма входа превращается в API для перебора пользователей.
Сессии, cookie и CSRF
После входа сессионная cookie и есть пользователь, поэтому она заслуживает такой же защиты, как пароль. Основную работу выполняют три атрибута cookie и один префикс в имени:
Secureозначает, что cookie никогда не передаётся по обычному HTTP, поэтому её нельзя перехватить в публичной сети Wi-Fi.HttpOnlyделает её невидимой для JavaScript, поэтому XSS-уязвимость не сможет её прочитать.SameSite=Lax(илиStrictдля админ-панелей) не даёт отправлять её в межсайтовых POST-запросах, что нейтрализует большинство CSRF-атак.- Префикс
__Host-заставляет браузер отклонять cookie, если у неё нет флагаSecure, путиPath=/или если задан атрибутDomain, поэтому поддомен не сможет её перезаписать.
Set-Cookie: __Host-session=9f1c7e2a...; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=28800Действия, изменяющие состояние, никогда не должны быть доступны через GET. Ссылку вроде /account/delete?id=42 может вызвать тег <img> на любой странице, которую посещает пользователь, какими бы хорошими ни были флаги ваших cookie.
CSRF: вторая линия защиты за SameSite
Межсайтовая подделка запроса (CSRF) заставляет браузер, в котором пользователь уже вошёл в систему, отправить на ваш сайт запрос, изменяющий состояние, с другой страницы. Cookie с SameSite останавливают типичный случай, но для каждого запроса, отличного от GET, сохраняйте вторую защиту: anti-CSRF-токен для сессии в скрытом поле или собственном заголовке либо проверку того, что заголовок Origin (или Sec-Fetch-Site) совпадает с вашим собственным источником. Большинство фреймворков (Django, Rails, Laravel, server actions в Next.js) делают это по умолчанию; ошибка — отключить защиту для API-эндпоинта, а затем вызывать этот эндпоинт из браузера.
Идентификаторы сессий, ротация и JWT
Генерируйте идентификаторы сессий минимум со 128 битами случайности, меняйте идентификатор при входе и при любом изменении привилегий, чтобы защититься от фиксации сессии, завершайте сессии после периода бездействия и дайте пользователям кнопку «выйти на всех устройствах», которая аннулирует сессии на сервере. JWT делайте короткоживущими (минуты, плюс refresh-токен, который можно отозвать), никогда не храните их в localStorage, где их может прочитать любой скрипт, и считайте утечку ключа подписи полноценным взломом, потому что теперь можно подделать любой токен, который им когда-либо был подписан.
Инъекции и XSS: проверяйте ввод, кодируйте вывод
Инъекции (A05:2025) и межсайтовый скриптинг — одна и та же ошибка в разных местах: данные от пользователя передаются интерпретатору (SQL, командной оболочке, LDAP-запросу, HTML-странице) так, будто это код. И исправление везде одинаковое: разделяйте данные и код и никогда не собирайте команду конкатенацией строк.
Параметризованные запросы вместо склейки строк
Для SQL используйте подготовленные выражения (prepared statements) или ORM, который их генерирует: тогда база данных воспринимает ввод как значение, которое никогда не может изменить структуру запроса. То же правило действует для команд оболочки (передавайте массив аргументов, а не строку), для фильтров NoSQL (отклоняйте объекты-операторы вроде {"$gt": ""} в пользовательском вводе) и для путей к файлам (нормализуйте путь и убедитесь, что он остаётся внутри нужного каталога).
// Vulnerable: the input becomes part of the query text
const rows = await db.query(`SELECT * FROM users WHERE email = '${email}'`);
// Safe: the driver sends the value separately from the query
const rows = await db.query("SELECT * FROM users WHERE email = $1", [email]);
// Python / psycopg: same idea
cur.execute("SELECT * FROM users WHERE email = %s", (email,))Кодирование вывода останавливает XSS
Межсайтовый скриптинг возникает, когда текст, контролируемый пользователем, записывается в страницу без кодирования под тот контекст, в который он попадает. Современные шаблонизаторы (React и JSX, Vue, Jinja2, Blade, ERB) кодируют по умолчанию; сегодня XSS обычно появляется через обходные механизмы: dangerouslySetInnerHTML, v-html, |safe, innerHTML, а также при сборке URL или встроенных обработчиков событий из пользовательских данных. Кодируйте под конкретный контекст, а форматированный HTML санитизируйте специализированной библиотекой, например DOMPurify, а не регулярным выражением.
| Куда попадают данные | Как кодировать | Пример |
|---|---|---|
| Тело HTML | HTML-сущности | < превращается в <, " — в " |
| Атрибут HTML | Сущности для атрибутов в кавычках | Всегда заключайте значение атрибута в кавычки; кодируйте " и ' |
| JavaScript | Не вставляйте данные в текст скрипта; передавайте их через атрибуты data- или JSON-блок <script type="application/json"> | </script> внутри строки превращается в \u003c/script\u003e |
| Параметр URL | Процентное кодирование | encodeURIComponent(value) |
| Значение CSS | Полностью избегайте; выбирайте имена классов из белого списка | Никогда не подставляйте пользовательский ввод в style |
SSRF, загрузка файлов и десериализация
Подделка запросов на стороне сервера (SSRF): если ваш сервер загружает URL, указанный пользователем (вебхуки, прокси для изображений, превью ссылок), атакующий может направить его на внутренние сервисы или на эндпоинт метаданных облака 169.254.169.254 и прочитать учётные данные. Сначала разрешайте имя хоста, отклоняйте частные и link-local диапазоны, отключайте редиректы и размещайте такие загрузчики в сегменте сети, из которого недоступно ничего внутреннего.
Загрузка файлов: проверяйте тип по содержимому, а не по расширению или Content-Type от клиента; ограничивайте размер; храните файлы вне корня веб-сервера или в объектном хранилище под случайным именем, которое генерируете вы; по возможности отдавайте их с отдельного источника с nosniff и Content-Disposition: attachment. Никогда не допускайте, чтобы загруженный файл оказался там, где веб-сервер будет его выполнять.
Десериализация и mass assignment: никогда не десериализуйте недоверенные данные в формате, который может создавать экземпляры произвольных классов (нативная сериализация Java, pickle в Python, unserialize в PHP, YAML с пользовательскими тегами); используйте JSON со схемой. Привязывайте тело запроса к явному белому списку полей, чтобы пользователь не мог отправить POST с "role": "admin" в модель, у которой случайно есть такой столбец.
Broken Access Control: риск номер один
Broken Access Control (нарушение контроля доступа) занимает первое место в OWASP Top 10 с 2021 года и сохраняет его как A01:2025. Это не одна ошибка, а привычка: при каждом запросе проверять, кто перед вами (аутентификация), и забывать проверить, к чему этому человеку можно обращаться (авторизация). Классическая форма — небезопасная прямая ссылка на объект (IDOR): GET /api/invoices/1042 работает для владельца счёта, а заодно и для любого, кто поменяет номер.
- Запрет по умолчанию. Каждый маршрут требует явного правила, разрешающего доступ; отсутствие правила означает 403, а не 200.
- Проверяйте на сервере, для каждого объекта. Спрятать кнопку в интерфейсе — не контроль доступа. Каждое чтение и каждая запись должны подтверждать, что текущий пользователь владеет конкретной записью или имеет к ней доступ, — в том числе в массовых эндпоинтах, экспорте и фоновых задачах.
- Используйте неугадываемые идентификаторы там, где это помогает (UUID), но никогда не полагайтесь на них: скрытность — не авторизация.
- Ограничьте CORS.
Access-Control-Allow-Origin: *вместе с учётными данными или отражение любогоOrigin, который присылает запрос, отдаёт ваш API любому сайту, который посещает пользователь. - Отключите листинг каталогов, заблокируйте
.git,.env, файлы резервных копий и конфигурации на уровне веб-сервера и не выставляйте админ-панели в публичный интернет — или закройте их MFA и белыми списками IP. - Ограничивайте частоту и логируйте ошибки авторизации. Всплеск ответов 403 в одной сессии — это идущая атака перебора.
// Express: ownership check on every object access
app.get("/api/invoices/:id", requireAuth, async (req, res) => {
const invoice = await Invoice.findById(req.params.id);
if (!invoice || invoice.ownerId !== req.user.id) {
return res.status(404).end(); // 404, not 403: don't confirm the record exists
}
res.json(invoice);
});Уровень 5: зависимости и цепочка поставок ПО
Большая часть кода в типичном веб-приложении — это код, который вы скачали, а не написали. Поэтому Software Supply Chain Failures впервые появились на позиции A03 в OWASP Top 10 2025, а эксплуатация уязвимостей в DBIR 2026 обогнала фишинг и украденные учётные данные. В сентябре 2025 года после фишинговой атаки на одного сопровождающего в chalk, debug и 16 других npm-пакетов, которые вместе скачивают более двух миллиардов раз в неделю, внедрили вредоносный код, ворующий криптовалюту; каждый проект, который в это окно установил непривязанную версию, получил его автоматически.
- Коммитьте lockfile (
package-lock.json,yarn.lock,pnpm-lock.yaml,poetry.lock,go.sum) и устанавливайте зависимости в CI черезnpm ci, чтобы сборки были воспроизводимыми, а новый upstream-релиз не мог проскочить без проверки. - Сканируйте постоянно:
npm audit,pip-audit,bundler-audit, GitHub Dependabot или Renovate для обновлений и сканер контейнеров, например Trivy или Grype, для уровня ОС. - Откладывайте обновления, не связанные с безопасностью, на несколько дней (
minimumReleaseAgeв Renovate), чтобы отравленный релиз, как правило, успели отозвать до того, как вы его примете, но патчи безопасности применяйте немедленно. - Фиксируйте сторонние actions и скрипты на SHA коммита в CI и используйте Subresource Integrity (
integrity="sha384-...") для каждого скрипта, который загружаете с публичного CDN. - Давайте CI минимальные привилегии: токены только для чтения там, где это возможно, короткоживущие учётные данные OIDC вместо долгоживущих секретов и права на публикацию только у задачи релиза.
- Генерируйте SBOM (CycloneDX или SPDX) при сборке, чтобы ответить на вопрос «нас это затрагивает?» за минуты, когда появится следующая CVE масштаба Log4Shell.
# Reproducible install + audit in CI
npm ci --ignore-scripts
npm audit --audit-level=high
# Pin a GitHub Action to a commit, not a floating tag
# uses: actions/checkout@v4 <- can change underneath you
# uses: actions/checkout@<full-commit-sha> # v4.2.2
# Subresource Integrity for a CDN script (hash from: openssl dgst -sha384 -binary lib.min.js | openssl base64 -A)
<script src="https://cdn.example.com/lib.min.js"
integrity="sha384-<base64-hash>"
crossorigin="anonymous"></script>npm ci --ignore-scripts блокирует скрипты, выполняемые при установке, через которые запускается большинство вредоносных npm-пакетов; затем снова включите скрипты только для тех немногих пакетов, которым действительно нужен этап сборки.
Если у вас WordPress или другая CMS, те же правила относятся к плагинам и темам — именно с них начинается большинство взломов CMS. Обновляйте ядро и все плагины, удаляйте неиспользуемые и проверьте, что сканер может узнать о ваших версиях, с помощью инструмента Определение CMS.
Уровень 6: защита сервера (порты, патчи, секреты, резервные копии)
Уровень приложения ничего не значит, если сервер под ним принимает вход по паролю через SSH, держит базу данных на публичном порту или не обновлялся с момента установки. Лучшие практики для серверов скучны — и именно через их отсутствие проникают программы-вымогатели.
Закройте все порты, которые вы не обслуживаете
Публичному веб-серверу нужны открытые порты 80 и 443 и SSH только с вашего диапазона IP-адресов. Базы данных (3306, 5432, 27017), Redis (6379), Elasticsearch (9200), админ-панели и эндпоинты метрик должны слушать только localhost или частную сеть. После каждого изменения проверяйте сервер снаружи с помощью проверки портов: именно так его видит атакующий, а группы безопасности в облаке легко истолковать неправильно.
# Ubuntu: default-deny firewall, allow only web + SSH from your office range
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow from 203.0.113.0/24 to any port 22 proto tcp
sudo ufw enable # check your own IP is in that range first, or you lock yourself out
# SSH: keys only, no root password login, in a drop-in that sshd reads first
printf 'PasswordAuthentication no\nPermitRootLogin prohibit-password\n' \
| sudo tee /etc/ssh/sshd_config.d/00-hardening.conf
sudo sshd -T | grep -E '^(passwordauthentication|permitrootlogin) ' # what sshd will really use
sudo systemctl reload sshВ Ubuntu и Debian sshd читает файлы из /etc/ssh/sshd_config.d/ раньше остальной части sshd_config, и для каждой настройки побеждает первое прочитанное значение. Поэтому команды выше пишут drop-in с именем 00-hardening.conf: правка ниже в основном файле проиграет drop-in облачного образа, где указано PasswordAuthentication yes. Проверьте результат через sudo sshd -T, прежде чем выходить из сессии. В проверке портов порт, который файрвол отбрасывает, показан как Фильтруется (нет ответа), а порт, на котором никто не слушает, как Закрыт: оба варианта нормальны, а исправлять нужно Открыт.
Патчи по расписанию, секреты — вне кода
Включите автоматические обновления безопасности для операционной системы, подпишитесь на рассылки по безопасности для вашей среды выполнения и фреймворка и считайте критическую CVE в любом компоненте, доступном из интернета, задачей на тот же день. Запускайте сервисы от непривилегированных пользователей, в контейнерах или в песочнице systemd, чтобы скомпрометированный процесс не мог читать всю машину.
Секретам (паролям от баз данных, API-ключам, ключам подписи) не место в репозитории, в слое Docker-образа или в клиентском JavaScript. Загружайте их из переменных окружения, которые подставляются при деплое, или из менеджера секретов, меняйте их, когда уходит кто-то, у кого был доступ, и сканируйте историю репозитория инструментом вроде gitleaks: однажды закоммиченный ключ скомпрометирован навсегда, даже если коммит удалён.
Резервные копии, которые переживут вымогателя, и CDN перед сайтом
Следуйте правилу 3-2-1: три копии на двух разных носителях, одна — за пределами площадки, причём хотя бы одна копия должна быть неизменяемой или офлайн, чтобы вымогатель, добравшийся до сервера, не смог зашифровать и резервные копии. Шифруйте резервные копии, включайте в них базу данных и каталог загрузок и, что особенно важно, регулярно проверяйте восстановление. Резервная копия, из которой никто ни разу не восстанавливался, — это надежда, а не план. От времени восстановления зависит, станет ли инцидент простоем или концом компании.
Поставьте перед origin-сервером CDN или WAF (Cloudflare, Fastly, AWS CloudFront с WAF или аналог вашего хостинга). Он поглощает объёмные DDoS-атаки, применяет управляемые правила против типичных инъекций и вредоносных ботов, скрывает IP origin-сервера и даёт место для rate limiting и гео-правил без правок в приложении. Затем ограничьте файрвол origin-сервера так, чтобы к порту 443 напрямую можно было подключиться только с IP-диапазонов CDN; иначе атакующие просто обойдут его. Проверка хостинга показывает, какой CDN стоит перед сайтом и что публичные данные говорят о хостинге за ним.
Аутентификация почты: SPF, DKIM и DMARC
Веб-безопасность включает и почту, которая доставляет письма сброса пароля, счета и ответы поддержки. Без SPF, DKIM и DMARC кто угодно может отправить письмо от имени billing@yourdomain.com; с февраля 2024 года Google и Yahoo требуют все три от массовых отправителей, а с мая 2025 года то же самое делает Microsoft для Outlook.com. Весь набор — три TXT-записи в DNS:
yourdomain.com. TXT "v=spf1 include:_spf.google.com -all"
google._domainkey.yourdomain.com. TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkq..."
_dmarc.yourdomain.com. TXT "v=DMARC1; p=reject; rua=mailto:dmarc@yourdomain.com; adkim=s; aspf=s"DMARC p=reject — это ещё и защита бренда: только она не даёт фишинговой рассылке поставить ваш точный домен в поле From. Агрегированные отчёты (rua=) показывают, кто пытается это сделать.
Начните DMARC с p=none, чтобы собирать отчёты, затем переходите на p=quarantine и p=reject, когда все законные отправители (платформа рассылок, helpdesk, CRM) проходят выравнивание. Добавьте записи MTA-STS и TLS-RPT, чтобы принудительно шифровать доставку на ваши почтовые серверы. На доменах, которые никогда не отправляют и не принимают почту, опубликуйте null MX (MX 0 .), v=spf1 -all и запись DMARC с p=reject: null MX лишь сообщает, что домен не принимает почту, а подделки отклонять получателям позволяют SPF и DMARC. Проверьте каждую запись в проверке SPF-записи, проверке DKIM и проверке DMARC, а если доставляемость упала, проверьте свои IP-адреса отправки по спискам блокировок в проверке IP в чёрных списках.
Логируйте, мониторьте и готовьтесь к взлому
Две из десяти категорий OWASP 2025 посвящены тому, что происходит после ошибки: Security Logging and Alerting Failures (A09, сбои логирования и оповещений) и новая Mishandling of Exceptional Conditions (A10, неправильная обработка исключительных ситуаций). Типичный взлом по-прежнему остаётся незамеченным месяцами, потому что доказательства так и не были записаны — или были записаны, но никто их не смотрел.
- Логируйте события безопасности с контекстом: каждый успешный и неудачный вход, изменения MFA, сбросы паролей, изменения прав, отказы в доступе, ошибки валидации ввода и действия администраторов — с меткой времени, ID пользователя, IP-адресом источника и user agent.
- Никогда не логируйте секреты: пароли, сессионные токены, полные номера карт, API-ключи. Маскируйте их на уровне логирования, а не в каждом месте вызова.
- Отправляйте логи за пределы сервера в централизованное хранилище (CloudWatch, Loki, Elastic, SIEM) со сроком хранения не менее 90 дней, чтобы атакующий с правами root не мог замести следы.
- Настраивайте оповещения на шаблоны, а не на отдельные события: 50 неудачных входов за минуту, всплеск ответов 403 в одной сессии, новый администратор, созданный в нерабочее время, сертификат для вашего домена, который вы не запрашивали.
- Закрывайтесь при сбое (fail closed): если зависимая проверка (сервис аутентификации, rate limiter, WAF) завершается ошибкой, отклоняйте запрос. Категория A10 существует потому, что очень многие системы предоставляют доступ, когда проверка, которая должна была его заблокировать, выбрасывает исключение.
- Мониторьте снаружи: доступность, срок действия сертификатов, изменения DNS, попадание в чёрные списки и регрессии заголовков. Запускайте заново проверку SSL-сертификата, проверку HTTP-заголовков и проверку DNS домена (40 проверок делегирования, DNS-серверов, DNSSEC и SPF/DMARC) после каждого деплоя.
Напишите план реагирования на инциденты заранее
Заранее решите, кто дежурит, как сменить все учётные данные, как перевести сайт в режим только для чтения, где лежат чистые резервные копии и каких регуляторов или клиентов нужно уведомить и в какие сроки (72 часа по GDPR). Раз в год проводите учения (tabletop). Отрепетированный план превращает дни импровизации в чек-лист.
Опубликуйте файл security.txt по адресу /.well-known/security.txt (RFC 9116) со строкой Contact: и датой Expires:: это два обязательных поля (ключ Encryption: необязателен). Исследователи, нашедшие ошибку на вашем сайте, первым делом посмотрят там; без него они могут не найти, кому сообщить.
Проверяйте: сканеры, пентесты и непрерывный контроль
Каждую из мер выше можно проверить, и проверку стоит по возможности автоматизировать, чтобы она не теряла актуальность. Разумная лестница — от бесплатных и мгновенных проверок до периодических и платных:
| Проверка | Инструмент | Как часто |
|---|---|---|
| Конфигурация TLS, цепочка, срок действия | Проверка SSL-сертификата, SSL Labs, testssl.sh | После каждого изменения сертификата или сервера; срок действия — ежедневно |
| Заголовки безопасности и CSP | Проверка HTTP-заголовков, MDN HTTP Observatory | После каждого деплоя (добавьте в CI) |
| Открытые порты | Проверка портов, nmap снаружи вашей сети | После каждого изменения файрвола или облачных настроек |
| DNS, DNSSEC, аутентификация почты | Проверка DNS домена, Проверка DMARC | Ежемесячно и после любого изменения DNS |
| Устаревшие поддомены | Поиск поддоменов | Ежеквартально и при каждом выводе сервиса из эксплуатации |
| Зависимости с известными уязвимостями | npm audit, Dependabot, Trivy | При каждой сборке |
| Уязвимости на уровне кода (SAST) | Semgrep, CodeQL | В каждом pull request |
| Уязвимости работающего веб-приложения (DAST) | ZAP, Nuclei | Еженедельно на staging |
| Авторизация и бизнес-логика | Ручной пентест или программа bug bounty | Ежегодно и после выпуска крупных функций |
Сканеры хорошо находят инъекции, проблемы с заголовками и устаревшие компоненты и плохо — ошибки авторизации и бизнес-логики, поэтому заложите в бюджет хотя бы один ручной тест в год для всего, что работает с деньгами или персональными данными. Исправляйте в порядке доступности для атаки: первым делом — всё, что может эксплуатировать неаутентифицированный пользователь из интернета.
Чек-лист безопасности сайта
Скопируйте его в README проекта или в трекер задач. Каждая строка — одна из практик выше, сформулированная так, чтобы её можно было отметить как выполненную или невыполненную; последний столбец — доказательство.
| Уровень | Практика | Готово, когда |
|---|---|---|
| Домен | Registrar lock и MFA в аккаунте регистратора; автопродление включено | WHOIS показывает clientTransferProhibited; до окончания регистрации больше 12 месяцев |
| DNS | Зона подписана DNSSEC; запись CAA перечисляет только ваши центры сертификации | dig +dnssec возвращает RRSIG; DS-запись есть в родительской зоне |
| DNS | Нет «висячих» записей CNAME или A | Каждое имя хоста из поиска поддоменов указывает на ресурс, который вы контролируете |
| TLS | Только TLS 1.2+, предпочтительно TLS 1.3, отдаётся полная цепочка | Проверка SSL-сертификата: цепочка полная, TLS 1.0/1.1 отклоняются |
| TLS | HSTS с max-age на год и includeSubDomains (preload по желанию) | Проверка HTTP-заголовков: тест HSTS пройден |
| TLS | Продление автоматизировано через ACME; срок действия отслеживается | certbot renew --dry-run проходит; оповещение настроено за 14 дней |
| Заголовки | CSP (на основе nonce), frame-ancestors, nosniff, Referrer-Policy, Permissions-Policy | Проверка HTTP-заголовков: оценка A или A+ |
| Аутентификация | Хеширование Argon2id или bcrypt; правила паролей NIST; проверка по базам утечек | Один хеш вычисляется 100–250 мс; нет правил состава |
| Аутентификация | MFA доступна всем и обязательна для администраторов; поддерживаются passkeys | Вход администратора невозможен без второго фактора |
| Аутентификация | Для эндпоинтов входа, сброса пароля и MFA действует rate limiting по IP и по учётной записи | Шесть быстрых попыток с одного IP: шестая возвращает 429 |
| Сессии | Cookie с префиксом __Host- и флагами Secure, HttpOnly, SameSite; ID меняется при входе | Атрибуты cookie видны в DevTools; старая сессия недействительна после входа |
| Ввод данных | Параметризованные запросы везде; вывод кодируется по контексту; загрузки проверяются по содержимому | Поиск по коду не находит запросов, собранных из строк; XSS-пейлоады отображаются как текст |
| Контроль доступа | Маршрутизация с запретом по умолчанию; проверка владения для каждого объекта; строгий CORS | Повтор запросов с ID другого пользователя возвращает 404 или 403 |
| Зависимости | Lockfile в репозитории; npm ci; аудит в CI; actions зафиксированы на SHA; SRI для скриптов с CDN | Сборка падает при CVE высокой критичности |
| Сервер | Публично открыты только 80 и 443; SSH только по ключам; автоматические обновления безопасности; секреты в env или vault | Проверка портов показывает 3306 и 6379 как «Закрыт» или «Фильтруется», и 22 тоже, если проверять не из вашего диапазона |
| Резервные копии | 3-2-1 с одной неизменяемой копией; восстановление проверено | Последняя успешная проверка восстановления — не старше 90 дней |
| Почта | SPF -all, DKIM, DMARC p=reject, MTA-STS | Проверка DMARC пройдена; агрегированные отчёты приходят |
| Мониторинг | События аутентификации и авторизации логируются централизованно; оповещения на шаблоны; внешний мониторинг доступности, сертификатов и DNS | Тестовое оповещение отправлено и получено |
| Реагирование | Письменный план реагирования на инциденты; опубликован security.txt; проведены штабные учения | План пересматривался в течение последних 12 месяцев |
Как это соотносится с OWASP Top 10:2025
OWASP Top 10 — самый цитируемый список рисков безопасности веб-приложений; в редакции 2025 года порядок изменился и появились две новые категории. Если клиент, аудитор или стандарт соответствия спросит, как вы закрываете эти риски, вот таблица соответствия практикам из этого руководства:
| OWASP Top 10:2025 | Практики в этом руководстве |
|---|---|
| A01 Broken Access Control (нарушение контроля доступа) | Запрет по умолчанию, проверки для каждого объекта, строгий CORS, закрытые админ-панели |
| A02 Security Misconfiguration (небезопасная конфигурация) | Заголовки безопасности, HSTS, закрытые порты, удалённые заголовки с версиями, отключённый листинг каталогов |
| A03 Software Supply Chain Failures (сбои цепочки поставок ПО) — новая | Lockfile, аудит, зафиксированные actions, SRI, SBOM, CI с минимальными привилегиями |
| A04 Cryptographic Failures (криптографические ошибки) | TLS 1.2+ и 1.3, HSTS, Argon2id, управление секретами, шифрование резервных копий |
| A05 Injection (инъекции) | Параметризованные запросы, кодирование вывода, CSP, проверка загружаемых файлов |
| A06 Insecure Design (небезопасное проектирование) | Модель угроз для каждого уровня, закрытие при сбое по умолчанию, rate limiting на этапе проектирования |
| A07 Authentication Failures (ошибки аутентификации) | Правила паролей NIST, проверка по базам утечек, MFA и passkeys, ротация сессий |
| A08 Software or Data Integrity Failures (нарушение целостности ПО и данных) | SRI, подписанные и зафиксированные зависимости, безопасная десериализация |
| A09 Security Logging and Alerting Failures (сбои логирования и оповещений) | Централизованное логирование, оповещения на шаблоны, внешний мониторинг |
| A10 Mishandling of Exceptional Conditions (неправильная обработка исключительных ситуаций) — новая | Закрытие при сбое, единообразные ответы об ошибках, никаких трассировок стека для пользователей |
OWASP ASVS (Application Security Verification Standard) превращает тот же материал в пронумерованный чек-лист для аудитов. Уровень ASVS L1 — реалистичная цель для любого публичного сайта; L2 — для всего, что обрабатывает персональные или финансовые данные.
Проверьте заголовки безопасности своего сайта за секунды
Бесплатная проверка HTTP-заголовков от DNS Robot показывает заголовки ответа любого URL ровно так, как их отправляет сервер, и оценивает заголовки безопасности тестами Mozilla HTTP Observatory — с исправлением для каждого недостающего. Без регистрации.
Источники
- 2026 Data Breach Investigations Report, Verizon
- OWASP Top 10:2025, OWASP
- Password Storage Cheat Sheet, OWASP
- SP 800-63B-4: Authentication and Authenticator Management, NIST
- Ballot SC-081v3: reducing certificate validity and data reuse periods, CA/Browser Forum
- HTTPS by default, Google
- RFC 9116: A File Format to Aid in Security Vulnerability Disclosure, IETF
Частые вопросы о безопасности сайта
Какие меры безопасности сайта самые важные?
Что такое OWASP Top 10 и актуален ли он в 2026 году?
Какие HTTP-заголовки безопасности должен отправлять каждый сайт?
Достаточно ли HTTPS, чтобы сайт был защищён?
Как правильно хранить пароли пользователей?
Что такое Content Security Policy и нужна ли она моему сайту?
Как проверить безопасность сайта?
Почему срок действия TLS-сертификатов сокращается?
Как часто нужно обновлять зависимости сайта?
Действительно ли атакуют небольшие сайты?
Похожие статьи
Связанные инструменты
Проверка HTTP-заголовков
Анализ HTTP заголовков ответа и оценка заголовков безопасности
Проверка SSL сертификата
Проверка действительности SSL сертификата и деталей безопасности
Проверка портов
Тест доступности порта и определение работающих сервисов
Аудит DNS домена
Оценка DNS домена: делегирование, NS-серверы, SOA, DNSSEC и почта — 40 проверок
Проверка DMARC
Проверка политики DMARC и настроек выравнивания для любого домена
Поиск поддоменов
Поиск всех поддоменов любого домена



