API Security: защита ваших endpoints от атак

Comprehensive руководство по безопасности REST и GraphQL API

API — основная точка атаки современных приложений. 90% веб-приложений имеют уязвимости в API. Средняя стоимость утечки данных — $4.35 миллиона. Правильная защита API критична для бизнеса. OWASP API Security Top 10 1. Broken Object Level Authorization (BOLA) Самая распространенная уязвимость. Пользователь получает доступ к чужим данным. Уязвимый код: app.get('/api/users/:id', async (req, res) => { const user = await db.user.findUnique({ where: { id: req.params.id } }); res.json(user); // Нет проверки прав! }); Безопасный код: app.get('/api/users/:id', requireAuth, async (req, res) => { const userId = req.params.id; // Проверка: пользователь может видеть только свой профиль if (userId !== req.user.id && !req.user.isAdmin) { return res.status(403).json({ error: 'Forbidden' }); } const user = await db.user.findUnique({ where: { id: userId } }); res.json(user); }); 2. Broken Authentication Слабая аутентификация позволяет взлом аккаунтов. Best Practices: JWT с коротким TTL — 15 минут для access token Refresh tokens — долгоживущие, храним в httpOnly cookies Rate limiting — максимум 5 попыток входа за минуту MFA — двухфакторная аутентификация для критичных операций // JWT с refresh token const accessToken = jwt.sign( { userId: user.id }, process.env.JWT_SECRET, { expiresIn: '15m' } ); const refreshToken = jwt.sign( { userId: user.id }, process.env.REFRESH_SECRET, { expiresIn: '7d' } ); res.cookie('refreshToken', refreshToken, { httpOnly: true, secure: true, sameSite: 'strict', maxAge: 7 * 24 * 60 * 60 * 1000 }); 3. Excessive Data Exposure API возвращает больше данных, чем нужно. Плохо: // Возвращает весь объект user, включая password hash res.json(user); Хорошо: // Explicit serialization res.json({ id: user.id, name: user.name, email: user.email, // password, секреты НЕ включены }); 4. Mass Assignment Пользователь может изменить поля, которые не должен. Уязвимость: app.patch('/api/users/:id', async (req, res) => { await db.user.update({ where: { id: req.params.id }, data: req.body // Опасно! Пользователь может установить isAdmin: true }); }); Защита: const allowedFields = ['name', 'email', 'bio']; const updateData = {}; for (const field of allowedFields) { if (req.body[field] !== undefined) { updateData[field] = req.body[field]; } } await db.user.update({ where: { id: req.params.id }, data: updateData }); Rate Limiting Express Rate Limit: import rateLimit from 'express-rate-limit'; const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 минут max: 100, // 100 запросов message: 'Too many requests', standardHeaders: true, legacyHeaders: false, }); app.use('/api/', limiter); Разные лимиты для разных endpoints: const loginLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 5, // Только 5 попыток входа skipSuccessfulRequests: true, }); app.post('/api/login', loginLimiter, loginHandler); Input Validation Zod для валидации: import { z } from 'zod'; const userSchema = z.object({ email: z.string().email(), password: z.string().min(8).max(100), age: z.number().int().min(18).max(120), }); app.post('/api/users', async (req, res) => { try { const data = userSchema.parse(req.body); // data валидирован и типизирован } catch (error) { return res.status(400).json({ error: error.errors }); } }); SQL Injection защита: // Плохо - SQL injection const query = `SELECT * FROM users WHERE email = '${email}'`; // Хорошо - Prepared statements const user = await db.query( 'SELECT * FROM users WHERE email = $1', [email] ); CORS правильно import cors from 'cors'; const corsOptions = { origin: process.env.ALLOWED_ORIGINS.split(','), credentials: true, optionsSuccessStatus: 200, methods: ['GET', 'POST', 'PUT', 'DELETE'], allowedHeaders: ['Content-Type', 'Authorization'], }; app.use(cors(corsOptions)); HTTPS и Security Headers Helmet.js: import helmet from 'helmet'; app.use(helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], styleSrc: ["'self'", "'unsafe-inline'"], scriptSrc: ["'self'"], imgSrc: ["'self'", 'data:', 'https:'], }, }, hsts: { maxAge: 31536000, includeSubDomains: true, preload: true, }, })); API Keys Management Генерация безопасных API ключей: import crypto from 'crypto'; function generateApiKey() { return crypto.randomBytes(32).toString('hex'); } // Храним hash, а не сам ключ const apiKey = generateApiKey(); const hashedKey = crypto .createHash('sha256') .update(apiKey) .digest('hex'); await db.apiKey.create({ data: { key: hashedKey, userId: user.id, }, }); Middleware для проверки: async function requireApiKey(req, res, next) { const apiKey = req.headers['x-api-key']; if (!apiKey) { return res.status(401).json({ error: 'API key required' }); } const hashedKey = crypto .createHash('sha256') .update(apiKey) .digest('hex'); const keyRecord = await db.apiKey.findUnique({ where: { key: hashedKey }, include: { user: true }, }); if (!keyRecord) { return res.status(401).json({ error: 'Invalid API key' }); } req.user = keyRecord.user; next(); } GraphQL Security 1. Query Complexity Limiting: import { createComplexityLimitRule } from 'graphql-validation-complexity'; const complexityLimit = createComplexityLimitRule(1000, { onCost: (cost) => { console.log('Query cost:', cost); }, }); const server = new ApolloServer({ schema, validationRules: [complexityLimit], }); 2. Depth Limiting: import depthLimit from 'graphql-depth-limit'; const server = new ApolloServer({ schema, validationRules: [depthLimit(5)], }); 3. Query Whitelisting: const allowedQueries = new Set([ 'query GetUser { user { id name } }', // ... другие разрешенные запросы ]); function validateQuery(query) { if (!allowedQueries.has(query)) { throw new Error('Query not allowed'); } } Logging и Monitoring Структурированное логирование: import winston from 'winston'; const logger = winston.createLogger({ format: winston.format.json(), transports: [ new winston.transports.File({ filename: 'error.log', level: 'error' }), new winston.transports.File({ filename: 'combined.log' }), ], }); // Логирование запросов app.use((req, res, next) => { logger.info({ method: req.method, url: req.url, ip: req.ip, userId: req.user?.id, }); next(); }); Алерты на подозрительную активность: // Детект брутфорса const failedAttempts = new Map(); function checkBruteForce(ip) { const attempts = failedAttempts.get(ip) || 0; if (attempts > 10) { // Алерт в Slack/PagerDuty alertSecurityTeam({ type: 'brute_force', ip, attempts, }); } failedAttempts.set(ip, attempts + 1); setTimeout(() => { failedAttempts.delete(ip); }, 15 * 60 * 1000); // Сброс через 15 минут } Encryption Шифрование чувствительных данных: import crypto from 'crypto'; const ENCRYPTION_KEY = Buffer.from(process.env.ENCRYPTION_KEY, 'hex'); const IV_LENGTH = 16; function encrypt(text) { const iv = crypto.randomBytes(IV_LENGTH); const cipher = crypto.createCipheriv('aes-256-cbc', ENCRYPTION_KEY, iv); const encrypted = Buffer.concat([cipher.update(text), cipher.final()]); return iv.toString('hex') + ':' + encrypted.toString('hex'); } function decrypt(text) { const parts = text.split(':'); const iv = Buffer.from(parts[0], 'hex'); const encrypted = Buffer.from(parts[1], 'hex'); const decipher = crypto.createDecipheriv('aes-256-cbc', ENCRYPTION_KEY, iv); const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]); return decrypted.toString(); } Security Checklist ✅ HTTPS everywhere ✅ JWT с коротким TTL ✅ Rate limiting на всех endpoints ✅ Input validation ✅ Authorization checks ✅ CORS правильно настроен ✅ Security headers (Helmet) ✅ Sensitive data encrypted ✅ API keys hashed ✅ Logging всех запросов ✅ Regular security audits ✅ Dependency scanning Инструменты для тестирования OWASP ZAP — автоматическое сканирование уязвимостей Burp Suite — профессиональный пентест инструмент Postman — тестирование API Snyk — сканирование зависимостей Заключение: API security — это не одноразовая задача, а постоянный процесс. Внедряйте security с первого дня разработки. Используйте автоматические инструменты для сканирования. Проводите регулярные аудиты. Обучайте команду. Помните: один пропущенный endpoint может стоить миллионы. Защищайте ваши API как крепость.