Кодовая безопасность: JWT, 2FA, XSS, CSRF, SQL-инъекции и всё, что должен знать разработчик, чтобы не проснуться знаменитым
Автор: Алексей Пасечник (Arhellist). Канонический адрес: https://arhellist.ru/blog/kodovaya-bezopasnost-jwt-2fa-xss-csrf-sql-inektsii-i-vsyo-chto-dolzhen-znat-razrabotchik-chtoby-ne-prosnutsya-znamenitym/.
Опубликовано: 2026-07-03.
Твой код может быть идеальным. Твоя архитектура — эталонной. Но если ты забыл про безопасность на уровне кода — ты построил замок из песка. Прилив не заставит себя ждать.
В прошлый раз мы говорили о защите сервера: SSH, UFW, бэкапы, мониторинг целостности. Всё это — про инфраструктуру. Но есть ещё один слой, который лежит ближе к пользователю, ближе к данным, ближе к деньгам. Это кодовая безопасность.
Сегодня я расскажу про основные стандарты и практики, которые должен знать каждый разработчик, который выпускает код в прод. Без паники, без перегруза. Только база, которая спасёт тебя от большинства проблем.
🔑 JWT: не просто строка, а ключ к королевству JWT (JSON Web Token) — это стандарт для авторизации. Выглядит как длинная строка из трёх частей, разделённых точками. Внутри — информация о пользователе, подписанная секретным ключом.
Что важно знать:
Храни секрет правильно. Не в коде. В переменных окружения. И никогда не коммить .env в репозиторий.
Время жизни токена. accesstoken — 15-30 минут, refreshtoken — 7-30 дней. Чем короче — тем безопаснее. Да, пользователи будут перелогиниваться чаще. Но если токен украдут — у злоумышленника будет меньше времени на атаку.
Храни refreshtoken в httpOnly cookie. Не в localStorage, не в sessionStorage. Только в защищённых куках, которые не доступны JavaScript. Это защищает от XSS.
Алгоритм подписи. Используй RS256 или ES256 (асимметричные) вместо HS256 (симметричный). HS256 — общий секрет на всех серверах. Если один сервер скомпрометирован — все токены под угрозой.
Ротация - обеспечь замену одних токенов другими в хранилище при из экспирации. Иначе твое хранилище превратится в свалку протухших токенов.
🛡️ 2FA: второй рубеж обороны Двухфакторная аутентификация — это не "фича для гиков". Это стандарт. Особенно для администраторов и пользователей с доступом к деньгам.
Как работает:
Пользователь вводит логин и пароль.
Затем — одноразовый код из приложения (Google Authenticator, Authy) или SMS (менее безопасно, но лучше, чем ничего).
Код генерируется на основе общего секрета и текущего времени (TOTP — Time-based One-Time Password).
Почему это важно:
Пароль могут украсть, подобрать или перехватить.
Код меняется каждые 30 секунд и доступен только с устройства пользователя.
Даже если злоумышленник знает пароль — без второго фактора он не войдёт.
Где внедрять:
Административная панель — обязательно.
Личный кабинет пользователя с платежами — обязательно.
Вход в систему — желательно для всех.
💉 SQL-инъекции: старая, но не менее опасная SQL-инъекция — это когда пользователь вводит в поле формы не "Иван", а ' OR 1=1; DROP TABLE users; --. И если ты не защитился — база данных превращается в руины.
Как защититься:
Параметризованные запросы (Prepared Statements). Это когда ты передаёшь значения отдельно от самого запроса. БД понимает, что это данные, а не команда. В PostgreSQL — $1, $2. В Node.js с библиотеками типа pg или Sequelize — автоматически.
Никогда не склеивай строки. Никогда не пиши "SELECT FROM users WHERE name = '" + userName + "'". Это прямой путь к катастрофе.
Валидация входных данных. Не верь пользователю. Проверяй тип, длину, формат, допустимые символы.
Минимальные права для БД. Пользователь приложения должен иметь только те права, которые ему нужны: SELECT, INSERT, UPDATE, DELETE для своей схемы. Никаких DROP, ALTER, CREATE.
🌐 XSS: когда JavaScript становится оружием XSS (Cross-Site Scripting) — это когда злоумышленник вставляет свой JavaScript-код на твою страницу. Пользователь открывает страницу, скрипт выполняется и, например, крадёт его куки с токеном авторизации.
Типы XSS:
Reflected (отражённый). Скрипт передаётся через URL-параметры. Пользователь переходит по ссылке — скрипт выполняется.
Stored (хранимый). Скрипт сохраняется в базе данных (например, в комментариях) и выполняется при каждом просмотре страницы.
DOM-based. Скрипт манипулирует DOM через уязвимости в коде.
Как защититься:
Экранирование (escaping). При выводе пользовательского контента экранируй специальные символы: <, , &, ", '. Библиотеки типа React делают это автоматически, но не везде.
Content Security Policy (CSP). Заголовок, который говорит браузеру, откуда можно загружать скрипты, стили, изображения. Если злоумышленник вставит свой скрипт — браузер его не выполнит, потому что он не из разрешённого источника.
HttpOnly и Secure куки. Куки с токеном не должны быть доступны через JavaScript. Только сервер их видит.
Санитизация на сервере. Если ты хранишь HTML от пользователя (например, для редактора) — используй библиотеки для очистки, такие как DOMPurify.
🕵️ CSRF: атака из другого окна CSRF (Cross-Site Request Forgery) — это когда злоумышленник заставляет браузер пользователя отправить запрос на твой сервер, используя активную сессию. Например, пользователь зашёл на фишинговый сайт, а тот отправил запрос на смену пароля в твоём приложении.
Как защититься:
CSRF-токены. Генерируешь уникальный токен для каждой формы или запроса, хранишь его в сессии на сервере. При отправке формы проверяешь, что токен совпадает.
SameSite Cookie. Устанавливай куки с атрибутом SameSite=Strict или SameSite=Lax. Браузер не будет отправлять куки при запросах с других сайтов.
Проверка Referer/Origin заголовков. Сервер проверяет, откуда пришёл запрос. Если не с твоего домена — отбрасывает.
Используй POST для изменений. GET-запросы не должны менять состояние системы. Никогда.
👥 RBAC: кто есть кто в твоей системе RBAC (Role-Based Access Control) — это управление доступом на основе ролей. Пользователь получает права не напрямую, а через роль.
Пример:
Роль user — может читать свои данные, комментировать посты.
Роль moderator — может удалять комментарии других пользователей.
Роль admin — может управлять пользователями, настройками, платежами.
Что важно:
Минимальные привилегии. Пользователь должен иметь ровно столько прав, сколько нужно для выполнения его задач. И ни на шаг больше.
Разделение ролей. Админ не должен быть обычным пользователем с дополнительными правами. Это разные сущности.
Проверка на каждом уровне. Не доверяй клиенту. Даже если интерфейс показывает "кнопка скрыта" — проверяй права на сервере для каждого запроса.
Инспекция. Логируй, кто что делал. Если кто-то удалил важные данные — ты должен знать, кто это был.
💳 PCI DSS: если ты работаешь с картами PCI DSS (Payment Card Industry Data Security Standard) — это набор требований для компаний, которые обрабатывают данные банковских карт.
Основные требования (очень кратко):
Храни данные только при необходимости. Никогда не храни CVV. Никогда не храни PAN (номер карты) в открытом виде. Если хранишь — только зашифрованным.
Передавай данные только по зашифрованным каналам. TLS 1.2+.
Регулярно обновляй антивирусы и патчи.
Ограничь доступ к данным. Только те сотрудники, кому реально нужно.
Веди логи доступа к данным.
Регулярно тестируй систему на уязвимости.
Документируй политики безопасности.
Если ты принимаешь карты — ты обязан соответствовать. Но лучше вообще не хранить данные карт на своём сервере. Используй платежные шлюзы (Stripe, YooKassa, Tinkoff), которые принимают данные на своей стороне.
🛡️ WAF: щит перед твоим приложением WAF (Web Application Firewall) — это файрвол на уровне веб-приложений. Он анализирует HTTP-трафик и блокирует подозрительные запросы до того, как они достигнут твоего кода.
Что умеет WAF:
Блокировать SQL-инъекции на уровне запроса.
Блокировать XSS-атаки.
Блокировать ботов и сканеры уязвимостей.
Ограничивать частоту запросов (rate limiting).
Блокировать IP из чёрных списков.
Популярные решения:
Cloudflare WAF — облачный, настраиваемый, с коллективной защитой.
ModSecurity — open-source, работает с Nginx/Apache.
AWS WAF — для тех, кто в облаке Amazon.
Fail2ban/CrowdSec — частично решают задачи, но это не полноценный WAF.
Когда нужен WAF:
Всегда, если у тебя есть публичное веб-приложение.
Особенно если ты обрабатываешь платежи или личные данные.
🧹 Санитизация входных данных: не верь никому, даже себе «Пользователь — это враг. Не потому что он злой. А потому что он случайно введёт "' OR 1=1 --" в поле "Имя" и сломает тебе всю базу, даже не заметив этого.» — Моя памятка на рабочем столе
Когда я говорю «санитизация», я не про «помыть руки перед едой». Я про то, как ты обрабатываешь данные, которые приходят от пользователя, прежде чем делать с ними что-то важное: записать в БД, отправить в email, вывести на страницу, передать в системный вызов.
Это критично. Потому что пользователь — это не злоумышленник. Но злоумышленник — это тоже пользователь.
Что такое санитизация и чем она отличается от валидации Эти два понятия путают даже опытные разработчики. Давай разберёмся раз и навсегда.
Валидация — это проверка на «правильность». Ты спрашиваешь: «А соответствует ли это тому, что я ожидаю?»
Это email? → Да/Нет.
Это число от 1 до 100? → Да/Нет.
Это строка не длиннее 255 символов? → Да/Нет.
Санитизация — это очистка и приведение к безопасному виду. Ты спрашиваешь: «Как сделать так, чтобы эти данные не навредили, даже если они содержат что-то опасное?»
Удалил все теги HTML → <scriptalert(1)</script → alert(1)
Экранировал спецсимволы → O'Reilly → O''Reilly
Привёл к нужному типу → "42" → 42
Обрезал лишние пробелы → " hello " → "hello"
Обычно фреймворки типа React содержат модули санитизации.
Классические ошибки в санитизации Ошибка 1: Санитизировать только то, что видишь
Разработчики часто проверяют только поля формы, которые знают. А злоумышленник может добавить своё поле и отправить его. Валидируй и санитизируй все данные, а не только ожидаемые.
Ошибка 2: Использовать чёрные списки
«Я запрещу слова <script и DROP». Это не работает. Злоумышленник напишет <ScRiPt или DRoP. Или использует другой вектор атаки. Используй белые списки: разрешай только то, что безопасно.
Ошибка 3: Санитизировать только на клиенте
«У меня же React экранирует вывод». Да, по умолчанию. Но ты можешь случайно использовать dangerouslySetInnerHTML. Или забыть экранировать в атрибутах. Или данные могут прийти из другого источника. Не доверяй клиенту.
Ошибка 4: Игнорировать контекст
То, что безопасно в одном контексте, может быть опасно в другом. То, что безопасно для JSON, может быть опасно для HTML. То, что безопасно для SQL, может быть опасно для shell-команды. Используй санитизацию, адаптированную к контексту.
📊 Что ещё важно знать Rate Limiting: Ограничение числа запросов с одного IP или пользователя. Защита от брутфорса и DDoS.
Input Validation: Проверяй всё, что приходит от пользователя. Тип, формат, длина, допустимые символы. Не доверяй никому.
Error Handling: Не показывай пользователю детали ошибки. "Что-то пошло не так" — достаточно. Полные стектрейсы — только в логи.
Logging: Логируй всё, что связано с безопасностью: неудачные попытки входа, смену паролей, повышение прав, доступ к чувствительным данным.
Session Management: Сессии должны истекать. И пользователь должен иметь возможность выйти. Храни сессии в защищённых куках.
Secure Headers: X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Content-Security-Policy, Referrer-Policy, Permissions-Policy, HSTS.
💎 Итог Безопасность — это не про "поставить firewall и забыть". Это про слои:
Инфраструктура — сервер, SSH, UFW, бэкапы.
Сеть — WAF, rate limiting, DDoS-защита.
Приложение — JWT, 2FA, RBAC, валидация.
Данные — шифрование, минимальные права, логирование.
Процессы — регулярные аудиты, обновления, обучение команды.
И самое главное: безопасность — это не разовое действие. Это процесс. Это дисциплина. Это культура.
Ты никогда не будешь на 100% защищён. Но ты можешь сделать так, чтобы взломать тебя было слишком сложно, слишком дорого и слишком долго. Чтобы злоумышленник просто пошёл искать более лёгкую цель.
Будь той целью, которую не хочется трогать.
О чём пишем
- Архитектура и эксплуатация веб-продуктов
- AI-агенты, RAG и практические контуры
- Кейсы LMS, маркетплейсов и защищённых коммуникаций
- Заметки по Next.js, Node.js, PostgreSQL и realtime