Перейти к содержимому

14. HTTPS и Security Headers

HTTPS — HTTP поверх TLS (Transport Layer Security). Шифрует трафик между браузером и сервером.

Без HTTPS:

  • Пароли, токены, куки — всё видно в сети
  • Провайдер может видеть и изменять трафик
  • MITM атаки становятся тривиальными
  • Браузеры помечают сайт как “Небезопасный”

Let’s Encrypt (бесплатно):

Окно терминала
# Certbot на Ubuntu/Debian
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
# Автоматическое обновление (добавляется автоматически)
sudo systemctl status certbot.timer

Cloudflare (CDN + SSL):

  • Настрой DNS на Cloudflare
  • Включи Proxy (оранжевая иконка)
  • SSL/TLS → Full (strict) — шифрование end-to-end
# Nginx — redirect HTTP → HTTPS
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# Современные настройки TLS
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
ssl_prefer_server_ciphers off;
# Session resumption
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
}

HTTP заголовки, инструктирующие браузер о политиках безопасности.

Браузер всегда использует HTTPS для сайта:

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
// Express
app.use((req, res, next) => {
res.setHeader(
'Strict-Transport-Security',
'max-age=31536000; includeSubDomains; preload'
);
next();
});
  • max-age=31536000 — 1 год
  • includeSubDomains — включая поддомены
  • preload — включение в браузерный preload list

Осторожно: после включения HSTS невозможно вернуться на HTTP без долгого ожидания!

Уже рассмотрели в уроке по XSS. Краткое напоминание:

Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-{random}'; style-src 'self' https://fonts.googleapis.com

Запрещает браузеру угадывать MIME тип:

X-Content-Type-Options: nosniff

Без этого заголовка браузер может выполнить файл как скрипт, даже если Content-Type: text/plain.

Запрещает загрузку сайта в iframe (защита от Clickjacking):

X-Frame-Options: DENY # никогда не показывать в iframe
X-Frame-Options: SAMEORIGIN # только в iframe с того же домена

Современная альтернатива через CSP:

Content-Security-Policy: frame-ancestors 'none'

Контролирует, какой Referer отправляется при переходах:

Referrer-Policy: no-referrer # никогда не отправлять
Referrer-Policy: same-origin # только внутри сайта
Referrer-Policy: strict-origin-when-cross-origin # (recommended)

Ограничивает доступ к браузерным API:

Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()

Для современных изолированных окружений:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Resource-Policy: same-origin

Нужны для SharedArrayBuffer и high-resolution timers.

Окно терминала
npm install helmet
import helmet from 'helmet';
app.use(helmet({
// Content-Security-Policy
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", 'https://fonts.googleapis.com'],
fontSrc: ["'self'", 'https://fonts.gstatic.com'],
imgSrc: ["'self'", 'data:', 'https:'],
connectSrc: ["'self'"],
frameSrc: ["'none'"],
objectSrc: ["'none'"],
},
},
// HSTS
strictTransportSecurity: {
maxAge: 31536000,
includeSubDomains: true,
preload: true,
},
crossOriginEmbedderPolicy: true,
crossOriginOpenerPolicy: { policy: 'same-origin' },
crossOriginResourcePolicy: { policy: 'same-origin' },
referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
}));
next.config.js
const securityHeaders = [
{
key: 'X-DNS-Prefetch-Control',
value: 'on',
},
{
key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload',
},
{
key: 'X-Frame-Options',
value: 'DENY',
},
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
key: 'X-XSS-Protection',
value: '1; mode=block',
},
{
key: 'Referrer-Policy',
value: 'strict-origin-when-cross-origin',
},
{
key: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=(), browsing-topics=()',
},
{
key: 'Content-Security-Policy',
value: [
"default-src 'self'",
"script-src 'self' 'unsafe-eval' 'unsafe-inline'", // unsafe нужен для Next.js
"style-src 'self' 'unsafe-inline'",
"img-src * blob: data:",
"media-src 'none'",
"connect-src *",
"font-src 'self'",
].join('; '),
},
];
module.exports = {
async headers() {
return [
{
source: '/(.*)',
headers: securityHeaders,
},
];
},
};

Контролирует, какие сайты могут обращаться к твоему API:

import cors from 'cors';
// Разрешить конкретные домены
app.use(cors({
origin: (origin, callback) => {
const allowedOrigins = [
'https://myapp.com',
'https://www.myapp.com',
process.env.NODE_ENV === 'development' ? 'http://localhost:3000' : null,
].filter(Boolean);
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true, // разрешить куки
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-CSRF-Token'],
maxAge: 86400, // кешировать preflight 24 часа
}));
// Не делай этого!
app.use(cors({ origin: '*', credentials: true })); // credentials не работает с *
app.use(cors()); // разрешает все — плохо для production API
Окно терминала
curl -I https://example.com | grep -E "(Strict|Content-Security|X-Frame|X-Content)"

Network → выбери запрос → Response Headers

ЗаголовокПриоритетЧто делает
HSTSКритичныйПринудительный HTTPS
Content-Security-PolicyКритичныйЗащита от XSS
X-Content-Type-OptionsВысокийЗапрет MIME sniffing
X-Frame-OptionsВысокийЗащита от Clickjacking
Referrer-PolicyСреднийКонтроль Referer
Permissions-PolicyСреднийОграничение API
CORSКритичный для APIКонтроль cross-origin
  1. Установи helmet в Express приложение и настрой все заголовки
  2. Проверь своё приложение на https://securityheaders.com
  3. Настрой Let’s Encrypt через certbot для домена
  4. Добавь Security Headers в next.config.js