СтатьяОткрытый материал

Безопасность веб-приложений: как защищаются данные, пользователи и сервер

Разбираем основу безопасности веб-приложений: активы, угрозы, уязвимости, атаки, риски, границы доверия, аутентификацию, авторизацию, входные данные, защиту HTTP и аудит.

Безопасность #HTTP #authentication #authorization #backend #безопасность #веб-разработка

Безопасность веб-приложений: как защищаются данные, пользователи и сервер

Безопасность веб-приложения часто представляют как список отдельных атак:

SQL Injection - внедрение SQL-кода через входные данные
XSS - выполнение чужого кода в браузере
CSRF - выполнение действия от имени авторизованного пользователя
Brute force - массовый перебор вариантов доступа

Но атака - только один элемент системы.

Чтобы понимать безопасность, сначала нужно определить:

  • что мы защищаем;
  • от каких действий защищаем;
  • где система может быть нарушена;
  • какие последствия допустимы, а какие нет;
  • какие механизмы должны предотвращать нарушение.

Без этой основы отдельные правила вроде «экранируй HTML» или «используй HTTPS» превращаются в набор несвязанных советов.

Что такое информационная безопасность

Информационная безопасность - защита информации и систем от недопустимого доступа, изменения, раскрытия, уничтожения и нарушения работы.

Обычно выделяют три базовых свойства:

Конфиденциальность - данные доступны только тем, кому они разрешены
Целостность - данные не изменены недопустимым образом
Доступность - система и данные доступны тогда, когда они нужны

Например:

Чужой пользователь прочитал адрес клиента - нарушена конфиденциальность
Злоумышленник изменил сумму заказа - нарушена целостность
Сервис перестал принимать запросы - нарушена доступность

Безопасность не сводится только к защите секретных данных. Иногда основная цель атаки - изменить информацию или сделать сервис недоступным.

Что защищает веб-приложение

Сначала нужно определить активы.

Актив - объект, потеря, раскрытие или изменение которого причиняет системе вред.

Активами могут быть:

  • учётные записи;
  • персональные данные;
  • заказы;
  • платежная информация;
  • документы;
  • API-ключи;
  • исходный код;
  • база данных;
  • вычислительные ресурсы;
  • административный интерфейс;
  • доступность системы.
Актив - то, что имеет ценность и должно быть защищено

Не все активы одинаково критичны.

Публичное изображение товара - низкая конфиденциальность
Пароль пользователя - критичные данные доступа
Ключ администратора - критичный секрет

Уровень защиты зависит от ценности актива и возможных последствий нарушения.

Угроза, уязвимость, атака и риск

Эти понятия связаны, но не являются синонимами.

Угроза - потенциальная причина нарушения безопасности
Уязвимость - слабое место системы
Атака - действие, использующее слабость системы
Риск - вероятность проблемы с учётом её последствий

Рассмотрим ситуацию, когда приложение строит SQL-запрос из строки пользователя:

query = f"SELECT * FROM users WHERE email = '{email}'"

Здесь:

Актив - данные пользователей
Угроза - попытка получить чужие данные
Уязвимость - ввод вставляется прямо в SQL
Атака - SQL Injection
Последствие - чтение или изменение данных

Сама уязвимость ещё не является атакой. Она создаёт возможность для неё.

Поверхность атаки

Поверхность атаки - все точки системы, через которые на неё можно воздействовать.

Для веб-приложения это могут быть:

  • публичные страницы;
  • формы;
  • API;
  • webhook endpoint;
  • административная панель;
  • загрузка файлов;
  • авторизация;
  • восстановление пароля;
  • внешние интеграции;
  • база данных;
  • файловое хранилище.
Новый endpoint - новая точка взаимодействия
Новая интеграция - новая граница между системами
Новый тип файла - новый вид внешних данных

Уменьшение поверхности атаки означает, что ненужные точки доступа не должны существовать.

Граница доверия

Граница доверия появляется там, где данные переходят между областями с разным уровнем доверия.

Браузер пользователя - сервер
Сервер - база данных
Backend - внешний API
Webhook-отправитель - webhook-получатель
Администратор - административный интерфейс

Данные не становятся безопасными только потому, что пришли из другого компонента системы.

Например, frontend может проверить:

Цена должна быть больше 0

Но сервер не должен доверять этой проверке. HTTP-запрос можно сформировать без пользовательского интерфейса.

Поэтому правила закрепляются на соответствующих уровнях:

Frontend - удобство и ранняя проверка
Backend - допустимость операции и права
База данных - структурная целостность данных

Почему нельзя доверять входным данным

Внешними данными являются не только значения из HTML-формы.

Источником может быть:

  • пользователь;
  • браузер;
  • мобильное приложение;
  • внешний API;
  • webhook;
  • файл;
  • URL;
  • cookie;
  • HTTP-заголовок.

Проверка должна отвечать на несколько вопросов:

Тип - соответствует ли значение ожидаемому типу
Формат - соответствует ли структура правилам
Диапазон - допустимо ли значение
Размер - не превышает ли данные ограничение
Права - может ли отправитель задавать это значение
Контекст - безопасно ли использовать данные дальше

Например:

quantity = 5 - корректно
quantity = -100 - тип корректный, значение недопустимо
quantity = "abc" - неверный тип

Валидация определяется не только синтаксисом, но и правилами предметной области.

Аутентификация и авторизация

Это разные этапы.

Аутентификация - определить, кто выполняет запрос
Авторизация - определить, разрешено ли ему действие

Пользователь может успешно войти в систему, но не иметь права открыть чужой заказ.

Пользователь известен - аутентификация пройдена
Пользователь может читать order_id=42 - требуется авторизация

Недостаточно проверить только факт входа:

if user.is_authenticated:
    return order

Нужно проверить право на конкретный ресурс и действие.

Пользователь вошёл - недостаточно
Пользователь имеет право на ресурс - необходимая проверка

Принцип минимальных привилегий

Каждому пользователю и компоненту дают только те права, которые нужны для его задачи.

Менеджер - работа с заказами
Бухгалтер - финансовые данные
Контент-менеджер - статьи
Администратор - системные операции

Если сервису нужно только читать таблицу, ему не требуется право удаления.

Необходимая операция - необходимое право
Лишнее право - дополнительный риск

Этот принцип относится к:

  • пользователям;
  • приложениям;
  • API-ключам;
  • сервисным аккаунтам;
  • базам данных;
  • файловым хранилищам.

Пароли

Пароль нельзя хранить в открытом виде.

Также его обычно не нужно хранить так, чтобы система могла восстановить исходное значение.

Для хранения используют алгоритмы password hashing.

Пароль - password hashing - сохранённое значение

При входе система проверяет новый ввод относительно сохранённого результата.

К паролю добавляют случайную соль.

Salt - случайное значение, используемое при вычислении хеша пароля

Для паролей применяют алгоритмы, специально рассчитанные на дорогое вычисление. Обычный быстрый SHA-256 сам по себе не является подходящей схемой хранения паролей.

Сессии и токены

После входа серверу нужно узнавать пользователя в следующих запросах.

Один из способов - сессия.

Вход - сервер создаёт сессию - браузер получает идентификатор - следующие запросы содержат идентификатор

Другой способ - токен.

Токен - значение, подтверждающее определённый контекст доступа

Наличие токена не отменяет авторизацию.

Токен определил пользователя - сервер проверил права - операция разрешена или отклонена

Cookie автоматически отправляются браузером на подходящий адрес.

Для чувствительных cookie используются дополнительные параметры:

HttpOnly - ограничивает доступ JavaScript к cookie
Secure - cookie передаётся по защищённому соединению
SameSite - ограничивает межсайтовую отправку cookie

Эти параметры решают разные задачи и не заменяют друг друга.

SQL Injection

SQL Injection появляется, когда внешние данные превращаются в часть SQL-кода.

Опасная схема:

SQL-код + строка пользователя - одна команда

Защита строится на разделении структуры запроса и данных.

SQL-команда - структура
Параметр - значение

Параметризованный запрос не должен интерпретировать значение как SQL-синтаксис.

ORM обычно делает это при стандартном использовании, но ручная сборка raw SQL строками может вернуть уязвимость.

XSS

XSS появляется, когда пользовательские данные попадают в HTML как исполняемый код.

Например, пользователь сохранил:

<script>...</script>

Если приложение вставит строку как доверенный HTML, браузер может выполнить код.

Основной принцип:

Пользовательские данные - данные
HTML приложения - код

Экранирование не позволяет специальным символам изменить структуру HTML.

Особое внимание требуется там, где приложение намеренно разрешает пользовательский HTML.

CSRF

Браузер может автоматически отправлять cookie вместе с запросом.

Из-за этого другая страница способна попытаться заставить браузер авторизованного пользователя выполнить действие на целевом сервисе.

CSRF-защита проверяет допустимость изменяющего запроса.

Cookie - подтверждает сессию
CSRF-механизм - помогает проверить допустимость происхождения действия

CSRF не заменяет авторизацию.

CORS и Same-Origin Policy

Браузер ограничивает взаимодействие JavaScript между разными origin.

Origin определяется сочетанием:

Схема + домен + порт

CORS позволяет серверу сообщить браузеру, каким внешним origin разрешено читать определённые ответы.

Важно разделять:

CORS - политика браузерного доступа
Авторизация - проверка права пользователя или клиента

CORS не является системой прав API.

HTTPS

HTTPS использует TLS для защиты передачи данных между сторонами.

Основные свойства:

Шифрование - скрывает содержимое трафика
Целостность - позволяет обнаружить изменение данных
Аутентификация сервера - сертификат помогает проверить сторону соединения

HTTPS защищает канал, но не исправляет ошибки логики приложения.

HTTPS + неправильная авторизация - данные всё равно могут быть раскрыты

Безопасность API

API принимает запросы непосредственно от программ, поэтому нельзя полагаться на ограничения пользовательского интерфейса.

Для endpoint нужно определить:

Кто может вызвать
Какие данные разрешены
Как определяется пользователь или сервис
Как проверяются права
Как ограничивается частота
Какие поля возвращаются
Что записывается в аудит

Сервер не должен возвращать чувствительные поля только потому, что они существуют в объекте модели.

Rate limiting

Даже корректный запрос может стать проблемой при слишком высокой частоте.

Rate limiting ограничивает количество операций за период.

Он особенно полезен для:

  • входа;
  • восстановления пароля;
  • отправки сообщений;
  • тяжёлых API-методов;
  • публичных endpoint;
  • внешних интеграций.
Частота допустима - запрос выполняется
Лимит превышен - запрос ограничивается

Webhook и подпись запроса

Webhook endpoint доступен внешнему сервису. Получателю нужно проверить, действительно ли запрос сформирован ожидаемой стороной.

Один из механизмов - подпись.

Raw body + секрет - HMAC - ожидаемая подпись
Заголовок webhook - полученная подпись
Совпадение - криптографическая проверка пройдена

Часто используется HMAC-SHA256.

SHA-256 - криптографическая хеш-функция
HMAC - механизм вычисления подписи с секретным ключом

Подпись обычно вычисляют по исходным байтам тела запроса, поэтому изменившееся представление JSON может дать другой результат.

Replay attack

Корректно подписанный запрос можно попытаться отправить повторно.

Если операция означает:

Начислить деньги
Создать платёж
Выдать доступ

повторное выполнение недопустимо.

Используются несколько механизмов:

Timestamp - запрос не должен быть слишком старым
Event ID или nonce - событие не должно быть обработано повторно
Idempotency - повторная доставка не создаёт второе бизнес-действие

Загрузка файлов

Файл является внешними данными.

Нельзя доверять только расширению:

photo.jpg - имя не гарантирует содержимое

При загрузке учитывают:

  • формат;
  • MIME type;
  • размер;
  • имя;
  • место хранения;
  • права доступа;
  • дальнейшую обработку.

Пользовательский файл не должен автоматически становиться исполняемой частью приложения.

Секреты

Секрет - значение, владение которым даёт доступ к защищённой операции или системе.

Пароль базы данных - secret
API key - secret
Webhook secret - secret
Private key - secret
Access token - secret

Секреты не следует хранить прямо в исходном коде.

Код - логика
Конфигурация - параметры окружения
Secret - защищённое значение

Особенно опасна публикация секрета в истории Git.

Логирование и аудит

Система должна позволять понять:

Что произошло
Когда произошло
Кто выполнил действие
К какому объекту обращались
Каков результат

Технический лог и audit log решают разные задачи.

Технический лог - как работала программа
Audit log - кто совершил значимое действие

Для аудита могут сохраняться:

user_id - пользователь
action - действие
object_id - объект
timestamp - время
result - результат
request_id - идентификатор запроса

В логи нельзя без необходимости помещать:

  • пароли;
  • полные access token;
  • приватные ключи;
  • секреты;
  • чувствительные данные.

Request ID

Один пользовательский запрос может пройти через несколько компонентов.

HTTP-запрос - request_id
Backend - тот же request_id
Внешний сервис - request_id
Логи - request_id

Это упрощает анализ ошибок и подозрительных действий.

Многоуровневая защита

Нельзя рассчитывать на один механизм.

Например, изменение заказа может защищаться несколькими слоями:

HTTPS - аутентификация - авторизация - валидация - ограничения базы - транзакция - аудит

Если один слой содержит ошибку, другой может уменьшить последствия.

Defense in depth - несколько независимых уровней защиты системы

Безопасность на разных уровнях приложения

Одна операция проходит через несколько компонентов.

Пользователь - интерфейс - HTTP - backend - база данных

Интерфейс

Интерфейс:

  • помогает вводить корректные данные;
  • скрывает недоступные действия;
  • показывает ошибки.

Но интерфейс не является границей безопасности.

Backend

Backend:

  • аутентифицирует;
  • авторизует;
  • валидирует;
  • выполняет бизнес-правила;
  • ограничивает действия.

База данных

База может закреплять:

NOT NULL - обязательность
UNIQUE - уникальность
CHECK - допустимость значения
FOREIGN KEY - ссылочную целостность
Transaction - атомарность связанных изменений

Ограничения базы защищают данные независимо от конкретного интерфейса приложения.

Как анализировать безопасность функции

Пусть существует операция:

DELETE /orders/42

Для неё можно последовательно спросить:

Актив - что защищаем
Пользователь - кто вызывает
Аутентификация - как он определён
Авторизация - может ли удалять этот заказ
Ввод - корректен ли order_id
Бизнес-правило - можно ли удалять заказ в текущем состоянии
Данные - не нарушатся ли связи
Аудит - будет ли действие зафиксировано

Так безопасность становится частью проектирования операции, а не дополнительной проверкой после реализации.

Что безопасность не гарантирует автоматически

Не существует одного фреймворка или библиотеки, после подключения которой приложение становится безопасным.

Остаются:

  • ошибки бизнес-логики;
  • неправильные права;
  • утечки секретов;
  • неверная конфигурация;
  • уязвимые зависимости;
  • человеческие ошибки;
  • ошибки интеграций.

Главная задача - понимать границы системы и каждое решение о доверии.

Итог

Безопасность веб-приложения начинается не с названий атак.

Основная цепочка:

Актив - угроза - уязвимость - атака - последствие - риск - защитный механизм

На уровне приложения защита строится из нескольких слоёв:

Входные данные - аутентификация - авторизация - защита HTTP - безопасная работа с данными - секреты - логирование - аудит

Главные принципы:

Не доверять внешним данным
Проверять права на конкретное действие
Выдавать минимально необходимые привилегии
Разделять код и данные
Не хранить секреты в открытом виде
Защищать канал передачи
Использовать несколько уровней защиты
Сохранять возможность расследовать значимые действия

Когда эта модель понятна, SQL Injection, XSS, CSRF, JWT, HTTPS и другие механизмы занимают конкретное место в общей системе защиты.

Продолжить

  1. 01 Как подключить внешний API к программе: методы, параметры, заголовки и тело запроса Разбираем, как подключить внешний API к Python-программе или автоматизации: найти endpoint, выбрать HTTP-метод, передать параметры, заголовки и JSON, прочитать ответ и обработать ошибки.
  2. 02 Что такое webhook и как сервисы сообщают друг другу о событиях Подробно разбираем принцип работы вебхуков, отличие от polling и обычного API-запроса, устройство webhook-запроса, ответы, повторную доставку, идемпотентность, безопасность и обработку ошибок.
  3. 03 FastAPI: как устроен современный Python API Подробное введение в FastAPI: ASGI, Uvicorn, маршруты, параметры, Pydantic, валидация, зависимости, async, ошибки, middleware, фоновые задачи, тестирование и структура проекта.
  4. 04 Django: как устроен Python-фреймворк для веб-приложений Разбираем Django с нуля: проект и приложения, маршрутизацию, представления, модели, ORM, шаблоны, формы, middleware, админ-панель и путь HTTP-запроса.
  5. 05 Цифровой продукт и интерфейс: как пользователь взаимодействует с системой Разбираем, что такое цифровой продукт и интерфейс, чем отличаются UI, UX, GUI, CLI и API, как пользовательский сценарий превращается в действие клиента, сервера и базы данных.

Обратная связь

Материал был полезен?

Нашли ошибку или неточность? Сообщить →