Как защитить Flask-приложение — пошаговое руководство

Sat Oct 11 2025

Это краткий чек-лист по укреплению безопасности Flask с фокусом на код, который поможет защитить приложение.

Краткий чек-лист

  • Секреты в переменных окружения или хранилище секретов (никогда не хранить в git)
  • HTTPS везде + HSTS (от 1 года и более)
  • Заголовки безопасности (CSP, X-Frame-Options, Referrer-Policy и т. д.)
  • Строгие cookie и сессии (Secure, HttpOnly, SameSite)
  • Защита CSRF для маршрутов, изменяющих состояние
  • Аутентификация с сильным хешированием (argon2/bcrypt) и MFA, где это возможно
  • Ограничение частоты запросов для аутентификации и чувствительных конечных точек
  • Валидация входных данных и безопасная загрузка файлов
  • Фиксация версий зависимостей и сканирование безопасности (pip-audit, bandit)
  • Корректный reverse proxy + WSGI (gunicorn/uwsgi) и контейнеры без root
  • Структурированные журналы аудита + оповещения

1) Конфигурация и секреты

# app/config.py
import os

class Settings:
    secret_key = os.environ["FLASK_SECRET_KEY"]
    session_cookie_secure = True
    session_cookie_httponly = True
    session_cookie_samesite = "Lax"  # или "Strict" для приложений в рамках одного сайта
    remember_cookie_duration = 0

settings = Settings()

Используйте менеджер секретов (AWS Secrets Manager, GCP Secret Manager, 1Password Connect) или хотя бы .env (только для разработки). Никогда не добавляйте секреты в git.

# .env (только для разработки)
FLASK_SECRET_KEY='change-me'
DATABASE_URL='postgresql://user:pass@localhost:5432/app'

2) HTTPS + HSTS (на уровне приложения)

flask-talisman — это легковесное расширение Flask, которое устанавливает для вас адекватные заголовки безопасности HTTP и может принудительно включать HTTPS/HSTS. Почему это важно:

  • Предотвращает понижение протокола и проблемы со смешанным содержимым (HSTS)
  • Снижает риск clickjacking и утечки информации (X‑Frame‑Options, Referrer‑Policy)
  • Предоставляет настраиваемую базовую CSP для ограничения XSS

Примечания:

  • Отключите принудительный HTTPS в локальной разработке или установите app.debug = True.

  • При работе за proxy убедитесь, что установлен X-Forwarded-Proto, чтобы Talisman мог определить HTTPS.

  • PyPI: Flask-Talisman

  • Установка:

pip install flask-talisman
# app/security.py
from flask import Flask
from flask_talisman import Talisman

csp = {
    'default-src': ["'self'"],
    'script-src': ["'self'", "'unsafe-inline'"],  # замените на nonces ниже для более строгой CSP
    'style-src': ["'self'", "'unsafe-inline'"],
    'img-src': ["'self'", 'data:'],
}

def apply_security(app: Flask) -> None:
    Talisman(
        app,
        content_security_policy=csp,
        force_https=True,
        strict_transport_security=True,
        strict_transport_security_max_age=31536000,
        frame_options='SAMEORIGIN',
        referrer_policy='no-referrer',
        session_cookie_secure=True,
        session_cookie_http_only=True,
    )

Для чувствительных приложений отдавайте предпочтение сессиям на стороне сервера (например, Redis), а не подписанным cookie на стороне клиента.

Почему:

  • Сессии на стороне сервера позволяют мгновенно делать сессии недействительными (выход из системы, отзыв доступа)
  • Вы избегаете больших, защищённых от подделки, но всё ещё допускающих повторное воспроизведение cookie на стороне клиента
  • Проще безопасно хранить дополнительный контекст аутентификации (роли, токены защиты от повторного воспроизведения)

Если вы вынуждены использовать сессии в cookie, делайте cookie краткоживущими, устанавливайте Secure+HttpOnly+SameSite и ротируйте ключи.

Библиотека: Flask-Session

Установка:

pip install Flask-Session
from flask import Flask
from flask_session import Session

def configure_sessions(app: Flask) -> None:
    app.config.update(
        SESSION_TYPE='redis',
        SESSION_COOKIE_SECURE=True,
        SESSION_COOKIE_HTTPONLY=True,
        SESSION_COOKIE_SAMESITE='Lax',
        PERMANENT_SESSION_LIFETIME=0,
    )
    Session(app)

4) Защита CSRF

Библиотека: Flask-WTF

Установка:

pip install flask-wtf

Почему:

  • CSRF использует cookie браузера; токены связывают действие пользователя с формой или страницей
  • SameSite=Lax помогает, но недостаточно для всех сценариев (например, некоторых OAuth/POST)
from flask_wtf import CSRFProtect

csrf = CSRFProtect()

def enable_csrf(app):
    app.config['WTF_CSRF_TIME_LIMIT'] = None
    csrf.init_app(app)

5) Аутентификация, хеширование и ограничение частоты запросов

Библиотеки:

  • Хеширование: werkzeug.security (встроенные PBKDF2/Scrypt). Для Argon2 используйте argon2-cffi
  • Ограничение частоты запросов: Flask-Limiter

Установка:

pip install Flask-Limiter
from werkzeug.security import generate_password_hash, check_password_hash
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address

limiter = Limiter(key_func=get_remote_address, default_limits=["200/min"])

def hash_password(pw: str) -> str:
    return generate_password_hash(pw, method='scrypt')  # или 'pbkdf2:sha256', 'bcrypt', 'argon2'

def verify_password(hash_: str, pw: str) -> bool:
    return check_password_hash(hash_, pw)

def protect_auth(app):
    limiter.init_app(app)

    @app.post('/login')
    @limiter.limit("5/minute")
    def login():
        ...

Почему:

  • Замедляет атаки credential stuffing и brute-force (Limiter)
  • Храните только хеши паролей; никогда не храните пароли в открытом виде. Отдавайте предпочтение memory-hard KDF

Необязательно (Argon2):

pip install argon2-cffi
from argon2 import PasswordHasher
ph = PasswordHasher()
hash_ = ph.hash(password)
ph.verify(hash_, candidate)

6) Валидация входных данных и загрузка файлов

Используйте pydantic/marshmallow, чтобы своевременно отклонять некорректные входные данные. Для файлов: проверяйте MIME по содержимому (magic), устанавливайте ограничения по размеру и разрешённым расширениям и рандомизируйте имена файлов.

Библиотеки:

  • pydantic или marshmallow
    • pydantic: быстрая валидация, модели на основе типов
    • marshmallow: явные схемы и контроль сериализации

Установите одну:

pip install pydantic
# или
pip install marshmallow
from pydantic import BaseModel, EmailStr, constr

class RegisterBody(BaseModel):
    email: EmailStr
    password: constr(min_length=12)

7) Content Security Policy (паттерн nonce)

Почему:

  • Строгая CSP с nonces блокирует внедрение inline/script и большинство отражённых XSS
  • Nonces работают лучше хешей, когда содержимое меняется при каждом запросе
from flask import g
import secrets

@app.before_request
def set_nonce():
    g.csp_nonce = secrets.token_urlsafe(16)

# Jinja: <script nonce="{{ g.csp_nonce }}">...</script>

Обновите CSP, добавив script-src 'self' 'nonce-{value}', и создавайте новый nonce для каждого запроса; замените все inline-обработчики событий на обычные скрипты.

8) Гигиена зависимостей (с фокусом на код)

pip install --upgrade pip pip-tools pip-audit bandit
pip-compile -o requirements.txt pyproject.toml  # или зафиксируйте версии с помощью pip-tools
pip-audit
bandit -r app/

9) Наблюдаемость и журналы

Формируйте журналы в JSON и отправляйте их в свою SIEM. Рассмотрите Sentry или OpenTelemetry для трейсов.

import json, logging, sys

handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(logging.Formatter('%(message)s'))

logger = logging.getLogger('app')
logger.setLevel(logging.INFO)
logger.addHandler(handler)

def audit(event: str, **kwargs):
    logger.info(json.dumps({'event': event, **kwargs}))

10) Безопасные значения по умолчанию, которые следует установить

app.config.update(
    JSONIFY_PRETTYPRINT_REGULAR=False,
    PROPAGATE_EXCEPTIONS=False,
    MAX_CONTENT_LENGTH=10 * 1024 * 1024,  # загрузки размером 10 MB
    SEND_FILE_MAX_AGE_DEFAULT=31536000,
)

11) Дополнительные фрагменты для укрепления безопасности

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

from flask import Flask, request

def extra_hardening(app: Flask):
    app.config['MAX_CONTENT_LENGTH'] = 10 * 1024 * 1024

    @app.after_request
    def remove_server_header(resp):
        resp.headers.pop('Server', None)
        return resp

    @app.before_request
    def block_weird_methods():
        if request.method not in { 'GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS' }:
            return ("", 405)

Финальный чек-лист перед запуском

  • TLS + HSTS включены и проверены
  • CSP применяется (сначала только отчёты, затем принудительное применение)
  • Cookie: Secure+HttpOnly+SameSite, минимальный срок жизни сессии
  • CSRF для всех небезопасных методов
  • Ограничения частоты запросов для маршрутов аутентификации и чувствительных маршрутов
  • Версии зависимостей зафиксированы, pip-audit без проблем, результаты bandit проверены
  • Журналы структурированы и отправляются; оповещения настроены