
• Оглавление
Атаки на веб‑приложения чаще происходят не из‑за «хакинга из фильмов», а из‑за мелочей: неконтролируемых скриптов, встраивания страниц в чужие сайты, утечек реферера, неверно настроенных ответов сервера. Правильные HTTP‑заголовки безопасности создают «пояс безопасности» поверх кода и инфраструктуры.
Что получает бизнес:
server {
listen 443 ssl http2;
server_name example.com;
# HSTS: вначале поставьте меньший max-age и только потом увеличьте и включите preload
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# Базовые заголовки безопасности
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
# CSP — пример для сайта с локальными скриптами и CDN шрифтами
# Начните с Report-Only, затем переключайте на Content-Security-Policy
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' fonts.googleapis.com; font-src 'self' fonts.gstatic.com; img-src 'self' data:; frame-ancestors 'none'; base-uri 'self'; form-action 'self'" always;
# Если хотите собирать отчёты о нарушениях CSP (совместите с Report-Only):
# add_header Content-Security-Policy-Report-Only "default-src 'self'; report-uri /csp-report" always;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /csp-report {
proxy_pass http://127.0.0.1:8000/csp-report;
}
}
# settings.py
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 31536000 # включайте постепенно
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SECURE_REFERRER_POLICY = "strict-origin-when-cross-origin"
SECURE_CONTENT_TYPE_NOSNIFF = True
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
# другие middleware
]
# Вариант CSP через django-csp (pip install django-csp)
INSTALLED_APPS = [
# ...
"csp",
]
MIDDLEWARE += ["csp.middleware.CSPMiddleware"]
CSP_DEFAULT_SRC = ("'self'",)
CSP_SCRIPT_SRC = ("'self'",) # без инлайна — скрипты из статических файлов
CSP_STYLE_SRC = ("'self'", "fonts.googleapis.com")
CSP_FONT_SRC = ("'self'", "fonts.gstatic.com")
CSP_IMG_SRC = ("'self'", "data:")
CSP_FRAME_ANCESTORS = ("'none'",)
CSP_BASE_URI = ("'self'",)
CSP_FORM_ACTION = ("'self'",)
# Чтобы перейти на nonce: используйте {% csp_nonce %} в шаблонах и внешний или инлайновый скрипт с nonce
# Пример шаблона:
# <script nonce="{{ request.csp_nonce }}">console.log('ok');</script>
Если вам нужен гибкий контроль над заголовками без пакета, можно выставлять их на уровень реверс‑прокси (Nginx) или через собственный middleware в Django — но удобнее и надёжнее использовать готовый django-csp.
// app.js
const express = require('express');
const crypto = require('crypto');
const helmet = require('helmet');
const app = express();
app.enable('trust proxy');
// Генерация nonce на каждый запрос
app.use((req, res, next) => {
res.locals.nonce = crypto.randomBytes(16).toString('base64');
next();
});
// Базовые заголовки
app.use(helmet({
referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
xContentTypeOptions: true,
frameguard: false, // будем использовать frame-ancestors в CSP
contentSecurityPolicy: false // зададим отдельно, чтобы добавить nonce
}));
// HSTS — включайте только за HTTPS и после тестирования субдоменов
app.use(helmet.hsts({ maxAge: 31536000, includeSubDomains: true, preload: true }));
// CSP с nonce и отчётами
app.use((req, res, next) => {
const nonce = res.locals.nonce;
const csp = [
"default-src 'self'",
`script-src 'self' 'nonce-${nonce}' 'strict-dynamic'`,
"style-src 'self' fonts.googleapis.com",
"font-src 'self' fonts.gstatic.com",
"img-src 'self' data:",
"frame-ancestors 'none'",
"base-uri 'self'",
"form-action 'self'",
// Отчёты о нарушениях (совместимость):
"report-uri /csp-report"
].join('; ');
res.setHeader('Content-Security-Policy', csp);
next();
});
app.use(express.json({ type: ['application/json', 'application/csp-report', 'application/reports+json'] }));
app.post('/csp-report', (req, res) => {
// Логику можно отправлять в SIEM/лог-хранилище
console.warn('CSP violation:', JSON.stringify(req.body));
res.status(204).end();
});
app.get('/', (req, res) => {
const nonce = res.locals.nonce;
res.send(`<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Secure App</title>
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" rel="stylesheet">
</head>
<body>
<h1>Здравствуйте!</h1>
<script nonce="${nonce}">console.log('CSP nonce ok');</script>
</body>
</html>`);
});
app.listen(3000, () => console.log('Server on http://localhost:3000'));
Задача nonce — разрешить конкретный инлайновый скрипт и запретить остальные. Браузер сравнивает значение атрибута nonce у тега
Ключевые правила:
Альтернатива — хеши (sha256-...): подходят для статического инлайна, но неудобны при частых изменениях.
curl -I https://example.com | sed -n '/Strict-Transport-Security\|Content-Security-Policy\|Referrer-Policy\|Permissions-Policy\|X-Content-Type-Options/p'
Метрики для бизнеса:
Пример: интернет‑ритейл с 500 тыс. визитов/сутки.
Где экономия:
Итог: корректно настроенные заголовки безопасности — это быстрая и недорогая мера, которая даёт ощутимую защиту и помогает экономить на инцидентах, не мешая развитию продукта.