OWASP Top 10 — это список наиболее серьёзных категорий рисков веб-приложений, который поддерживает сообщество OWASP. Его удобно читать как чек-лист для проектирования и ревью: по каждой категории есть понятные защитные практики. Эта статья написана с позиции защиты: мы не будем разбирать способы атак, а посмотрим, какие меры нужно внедрить на сервере srv-web01 и в самом приложении, как проверить, что они работают, и как заметить попытки обхода в журналах.

Категории и их порядок меняются от редакции к редакции, поэтому сверяйся с актуальной версией на сайте OWASP. Ниже мы сгруппируем меры по смыслу, а не по номерам. Пример окружения: Nginx на srv-web01 (192.168.10.20), приложение за ним, база данных во внутреннем сегменте 192.168.10.0/24.

Контроль доступа и аутентификация

Шаг 1. Проверка прав на сервере, а не в браузере

Нарушенный контроль доступа — самая распространённая проблема. Суть в том, что приложение проверяет права формально, например скрывает кнопку, но сервер не проверяет, имеет ли пользователь право на конкретный объект. Защита строится на нескольких правилах: запрет по умолчанию, проверка владельца объекта при каждом запросе, централизованная функция авторизации вместо разбросанных условий, непредсказуемые идентификаторы ресурсов там, где это уместно.

Практический приём — писать автоматические тесты на доступ: «пользователь А не может читать данные пользователя Б», «обычная роль не вызывает административные методы». Эти тесты запускаются в конвейере CI при каждом изменении. Обнаруживай нарушения по журналам: серия ответов 403 и 404 от одного клиента при переборе идентификаторов должна вызывать оповещение.

Шаг 2. Надёжная аутентификация и сессии

Используй проверенные библиотеки и стандарты, а не самописные механизмы. Пароли хранятся только в виде медленных хэшей с солью (argon2id, bcrypt, scrypt). Включай второй фактор для чувствительных ролей, ограничивай число попыток входа и добавляй задержки, не раскрывай, что именно неверно — логин или пароль. Идентификатор сессии должен быть длинным случайным, выдаваться заново после входа, иметь срок жизни и отзываться при выходе.

# Nginx: ограничение частоты запросов к странице входа
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;

server {
    location = /login {
        limit_req zone=login burst=5 nodelay;
        proxy_pass http://127.0.0.1:8080;
    }
}

Ограничение скорости не заменяет блокировку учётной записи, но сильно затрудняет автоматический перебор. Журналируй все события входа: успех, провал, смена пароля, включение и отключение второго фактора.

Работа с данными и вводом

Шаг 3. Инъекции: разделяй данные и команды

Инъекции возникают, когда пользовательский ввод попадает в запрос или команду как часть кода. Защита одна, и она универсальна: данные никогда не склеиваются с командой. Для баз данных это параметризованные запросы или ORM, для операционной системы — вызов программ с массивом аргументов без оболочки, для шаблонов — автоматическое экранирование вывода. К этому добавляется валидация ввода по белому списку: тип, длина, формат.

# Плохо: запрос собирается из строк
query = "SELECT * FROM users WHERE email = '" + email + "'"

# Хорошо: параметризованный запрос
cur.execute("SELECT id, name FROM users WHERE email = %s", (email,))

Учётная запись приложения в базе должна иметь только необходимые права: чтение и запись в свои таблицы, но не администрирование и не доступ к чужим схемам. Тогда даже при ошибке в коде ущерб ограничен. Для обнаружения подозрительных запросов включи на уровне WAF или журнала базы оповещения о синтаксических ошибках SQL и нетипично длинных параметрах.

Шаг 4. Защита данных при передаче и хранении

Весь трафик идёт по TLS, а старые версии протокола отключены. Включи HSTS, чтобы браузер всегда использовал HTTPS. Чувствительные данные шифруются при хранении, ключи вынесены из кода и репозитория, а ненужные данные просто не собираются: то, чего нет, не может утечь.

server {
    listen 443 ssl http2;
    server_name www.itpages.lab.local;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers off;
    ssl_certificate     /etc/ssl/itpages/fullchain.pem;
    ssl_certificate_key /etc/ssl/itpages/privkey.pem;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
}
server {
    listen 80;
    server_name www.itpages.lab.local;
    return 301 https://$host$request_uri;
}

Проверяй конфигурацию командой nginx -t и периодически тестируй качество TLS внешним сканером или утилитой testssl.sh на своих серверах.

Конфигурация и компоненты

Шаг 5. Заголовки безопасности и защита от XSS

Межсайтовый скриптинг предотвращается правильным экранированием вывода на стороне приложения. Браузерные заголовки добавляют вторую линию защиты. Политика Content-Security-Policy ограничивает источники скриптов, X-Content-Type-Options запрещает угадывание типа, а флаги cookie защищают сессию.

add_header Content-Security-Policy "default-src 'self'; script-src 'self'; object-src 'none'; frame-ancestors 'none'" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), camera=(), microphone=()" always;
server_tokens off;

Cookie сессии должны иметь флаги Secure, HttpOnly и SameSite=Lax (или Strict). Внедряй CSP сначала в режиме отчётов (Content-Security-Policy-Report-Only), чтобы увидеть, что сломается, и только потом включай блокировку. Для защиты от подделки межсайтовых запросов используй токены CSRF от фреймворка и проверку заголовка Origin.

Шаг 6. Безопасная конфигурация и минимальная поверхность

Многие инциденты — следствие небрежной конфигурации: включённый режим отладки, учётные данные по умолчанию, открытые панели управления, листинг каталогов, подробные сообщения об ошибках с трассировкой. Отдавай пользователю общее сообщение, а подробности пиши в журнал. Убери из публичного доступа служебные файлы, резервные копии и репозитории.

autoindex off;
location ~ /\.(?!well-known) { deny all; }
location ~* \.(bak|old|sql|swp|log)$ { deny all; }
error_page 500 502 503 504 /errors/generic.html;

Админка приложения не должна быть доступна из интернета: ограничь её адресами управляющей сети или вынеси за VPN.

location /admin/ {
    allow 192.168.10.0/24;
    deny all;
    proxy_pass http://127.0.0.1:8080;
}

Шаг 7. Управление зависимостями и цепочкой поставки

Современное приложение на 80-90 процентов состоит из чужих библиотек, поэтому их уязвимости становятся вашими. Фиксируй версии зависимостей, веди перечень компонентов (SBOM), включи автоматическую проверку зависимостей в CI и обновляй их регулярно. Используй только доверенные источники пакетов и проверяй целостность по хэшам.

# Node.js
npm audit --omit=dev
# Python
pip install pip-audit && pip-audit -r requirements.txt
# Контейнеры и файловая система
trivy fs --severity HIGH,CRITICAL .

Результаты проверок включай в общий процесс управления уязвимостями, о котором мы писали ранее: у каждой находки должен быть владелец и срок. Не забывай про CMS и плагины: заброшенный плагин с известной дырой — типичная точка входа.

Обнаружение и реагирование

Шаг 8. WAF, журналы и мониторинг

Межсетевой экран веб-приложений (например, ModSecurity с набором правил OWASP CRS) отсекает типовой вредоносный трафик и даёт видимость. Начни с режима «только обнаружение», проанализируй ложные срабатывания, затем включи блокировку. WAF — это дополнительный слой, а не замена исправления кода.

sudo apt install -y libnginx-mod-http-modsecurity modsecurity-crs
# /etc/nginx/modsec/main.conf
SecRuleEngine DetectionOnly
SecAuditEngine RelevantOnly
SecAuditLog /var/log/nginx/modsec_audit.log
# после настройки исключений:
# SecRuleEngine On

Что должно попадать в журналы: аутентификация, отказы в доступе, ошибки валидации, изменения прав и конфигурации, административные действия. Не пиши в журнал пароли, токены и персональные данные. Отправляй логи на srv-scan01 или в отдельную систему сбора и настрой правила оповещения: всплеск ответов 401, 403, 500, необычные User-Agent, обращения к служебным путям.

Шаг 9. Защита встроена в разработку

Безопасность дешевле на стадии проектирования. Добавь в процесс разработки моделирование угроз на старте крупных функций, ревью кода с чек-листом, статический анализ (SAST) и проверку зависимостей в каждом запросе на слияние, а динамическое сканирование (DAST) запускай на тестовом стенде перед релизом. Отдельно проверяй приложение сканером из srv-scan01 только на своих стендах и в согласованное время. Для учебных целей полезно развернуть намеренно уязвимое приложение в изолированной сети без доступа к рабочим системам, чтобы команда училась распознавать ошибки в коде.

Шаг 10. Безопасная обработка загрузок, ссылок и внешних запросов

Две частые зоны риска — загрузка файлов пользователями и запросы, которые сервер выполняет по адресу, переданному клиентом. Для загрузок: проверяй тип по содержимому, а не по расширению, ограничивай размер, переименовывай файлы случайными именами, храни их вне корня веб-сервера или в отдельном хранилище и никогда не исполняй. Для серверных запросов по внешним адресам: разрешай только белый список доменов, запрещай обращения к внутренним диапазонам и служебным адресам метаданных, ограничивай схемы и перенаправления.

client_max_body_size 10m;
location /uploads/ {
    root /srv/uploads;
    add_header Content-Disposition "attachment";
    add_header X-Content-Type-Options "nosniff" always;
    location ~* \.(php|phtml|cgi|pl|sh)$ { return 403; }
}

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

Шаг 11. Проверка защиты и учения

Внедрённые меры нужно проверять. Составь для srv-web01 список контрольных проверок: заголовки безопасности на месте, TLS без устаревших версий, админка недоступна снаружи, режим отладки выключен, зависимости без известных критичных уязвимостей, журналы собираются и по ним срабатывают оповещения. Автоматизируй простые проверки скриптом и запускай после каждого развёртывания.

curl -sI https://www.itpages.lab.local | grep -iE "strict-transport|content-security|x-content-type|server:"
curl -s -o /dev/null -w "%{http_code}\n" https://www.itpages.lab.local/admin/
nginx -T 2>/dev/null | grep -E "server_tokens|ssl_protocols"

Раз в полгода проводи учения: смоделируй инцидент, например «в журнале всплеск ошибок 500 и необычные запросы», и пройди весь путь от оповещения до разбора и исправления. Это покажет, где не хватает журналов, прав или контактов.

Шаг 12. Культура безопасной разработки

Технические меры работают, когда их поддерживает привычка команды. Назначь в каждой группе разработки человека, отвечающего за вопросы безопасности (security champion), и выдели ему время. Введи короткие обучающие сессии на реальных ошибках из собственного кода, без поиска виноватых. Добавь в определение «готово» пункты о проверке прав, валидации ввода и журналировании. Храни перечень типовых безопасных решений, например готовый модуль авторизации и шаблон конфигурации Nginx, чтобы разработчикам было проще сделать правильно, чем неправильно. Принцип простой: безопасный путь должен быть самым лёгким. Когда защита встроена в привычные инструменты, а не навязана отдельным процессом, количество ошибок падает, а скорость разработки не страдает.

Частые ошибки и как их избежать

  • Проверка прав только на фронтенде. Скрытая кнопка не защищает метод API. Любое действие проверяй на сервере и покрывай автоматическими тестами доступа.
  • Ставка на WAF вместо исправления кода. Правила обходятся и дают ложное спокойствие. Используй WAF как слой обнаружения и временную меру, а причину устраняй в приложении.
  • Забытые зависимости и режим отладки. Старый плагин, отладочная консоль и стандартные пароли остаются годами. Включи автоматическую проверку зависимостей, чек-лист релиза и регулярное сканирование конфигурации.

Что дальше

Защита строится из нескольких слоёв — вот соседние темы серии: