Введение
Безопасность веб-приложений — это не опция, а необходимость. Рассмотрим полный чек-лист безопасности для каждого разработчика.
1. HTTPS везде
server {
listen 80;
server_name example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
add_header Strict-Transport-Security "max-age=63072000" always;
}
2. Защита от XSS
// ❌ Опасно
element.innerHTML = userInput;
// ✅ Безопасно
function escapeHtml(text: string): string {
const map: Record<string, string> = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
return text.replace(/[&<>"']/g, char => map[char]);
}
element.textContent = userInput;
// React автоматически экранирует
function Comment({ text }: { text: string }) {
return <p>{text}</p>;
}
// С санитизацией HTML
import DOMPurify from 'dompurify';
const sanitizedHtml = DOMPurify.sanitize(html);
3. Content Security Policy
// next.config.js
const securityHeaders = [
{
key: 'Content-Security-Policy',
value: [
"default-src 'self'",
"script-src 'self' 'unsafe-inline'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
"connect-src 'self' https://api.example.com",
].join('; ')
}
];
4. Защита от CSRF
import { randomBytes } from 'crypto';
function generateCsrfToken(): string {
return randomBytes(32).toString('hex');
}
// Middleware проверки
function csrfProtection(req, res, next) {
if (['POST', 'PUT', 'DELETE'].includes(req.method)) {
const token = req.headers['x-csrf-token'];
if (token !== req.session.csrfToken) {
return res.status(403).json({ error: 'Invalid CSRF token' });
}
}
next();
}
5. SQL Injection
// ❌ Опасно
const query = `SELECT * FROM users WHERE id = ${userId}`;
// ✅ Параметризованные запросы
const user = await prisma.user.findUnique({
where: { id: userId }
});
// ✅ С raw SQL
const users = await prisma.$queryRaw`
SELECT * FROM users WHERE id = ${userId}
`;
6. Безопасность паролей
import bcrypt from 'bcrypt';
const SALT_ROUNDS = 12;
async function hashPassword(password: string): Promise<string> {
return bcrypt.hash(password, SALT_ROUNDS);
}
async function verifyPassword(password: string, hash: string): Promise<boolean> {
return bcrypt.compare(password, hash);
}
Чек-лист безопасности
- ✅ HTTPS на всех страницах
- ✅ CSP заголовки настроены
- ✅ CSRF токены для форм
- ✅ Параметризованные SQL запросы
- ✅ Пароли хэшируются (bcrypt)
- ✅ Rate limiting на API
- ✅ Валидация входных данных
- ✅ Безопасные HTTP заголовки
Безопасность — это процесс, а не состояние. Регулярно проводите аудит и обновляйте зависимости.
7. Rate Limiting
import rateLimit from 'express-rate-limit';
// Базовый rate limiter
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 минут
max: 100, // максимум 100 запросов
message: 'Too many requests, please try again later.',
standardHeaders: true,
legacyHeaders: false,
});
// Строгий лимит для авторизации
const authLimiter = rateLimit({
windowMs: 60 * 60 * 1000, // 1 час
max: 5, // 5 попыток
message: 'Too many login attempts',
});
app.use('/api/', limiter);
app.use('/api/auth/login', authLimiter);
8. Валидация входных данных
import { z } from 'zod';
// Схема валидации
const UserSchema = z.object({
email: z.string().email('Invalid email'),
password: z.string()
.min(8, 'Password must be at least 8 characters')
.regex(/[A-Z]/, 'Must contain uppercase')
.regex(/[0-9]/, 'Must contain number'),
age: z.number().min(18).max(120).optional(),
});
// Использование
async function createUser(req: Request, res: Response) {
const result = UserSchema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({
errors: result.error.flatten().fieldErrors
});
}
const validatedData = result.data;
// Безопасно использовать данные
}
9. Безопасные HTTP заголовки
import helmet from 'helmet';
app.use(helmet());
// Или настроить вручную
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
},
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true,
},
referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
}));
10. Логирование безопасности
import winston from 'winston';
const securityLogger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'security.log' }),
],
});
// Логирование подозрительной активности
function logSecurityEvent(event: string, details: object) {
securityLogger.warn({
timestamp: new Date().toISOString(),
event,
...details,
});
}
// Использование
logSecurityEvent('FAILED_LOGIN', {
ip: req.ip,
email: req.body.email,
userAgent: req.headers['user-agent'],
});
Аутентификация и авторизация
Аутентификация проверяет личность пользователя, авторизация — его права доступа. Используйте JWT токены или session cookies для хранения состояния.
Хэширование паролей
Никогда не храните пароли в открытом виде. Хэширование с bcrypt или Argon2 защищает данные даже при утечке базы. Шифрование данных в transit (HTTPS) и at rest обязательно.
Защита от уязвимостей
Основные типы атак и защита:
- XSS — санитизация пользовательского ввода
- CSRF — токены и проверка Origin
- SQL Injection — параметризованные запросы
Настройте CORS правильно — разрешайте только доверенные домены. Cookies должны иметь флаги HttpOnly, Secure и SameSite.