Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Безопасность: Hardening
security_hardening

Безопасность: Hardening

CSRF, XSS, SQLi, uploads, CSP, security headers и секреты

Безопасность Django в production

Статус: основная часть актуальна для Django 5.2/6.0. Встроенная Content Security Policy появилась в Django 6.0; для 5.2 политику задают на reverse proxy или поддерживаемым сторонним пакетом.

Django предоставляет безопасные механизмы, но не может угадать архитектуру приложения. ORM не спасёт raw SQL с конкатенацией, autoescape — HTML, помеченный safe, а CSRF middleware — действие, которое ошибочно выполняется через GET.

Hardening начинается не со списка флагов, а с модели угроз: какие данные важны, кто имеет к ним доступ, где проходят trust boundaries и как обнаружить нарушение. Настройки ниже — обязательные слои, но не замена этой модели.

#Поддерживаемая версия и проверка deployment

Устанавливайте последние security/bugfix-релизы выбранной поддерживаемой ветки Django и Python. Фиксируйте зависимости с проверяемыми lock-файлами, отслеживайте advisories и регулярно обновляйте не только Django, но и ASGI/WSGI server, драйвер базы, Pillow, parser и authentication-пакеты.

Перед deployment запускайте:

python manage.py check --deploy --settings=config.settings.production

System checks находят типовые ошибки настроек, но не знают, правильно ли proxy очищает заголовки, закрыт ли storage bucket и не допускает ли код horizontal privilege escalation. Результат проверки — начало ревью, а не сертификат безопасности.

#Базовые production settings

import os DEBUG = False SECRET_KEY = os.environ["DJANGO_SECRET_KEY"] ALLOWED_HOSTS = ["app.example.com"] CSRF_TRUSTED_ORIGINS = ["https://app.example.com"] SECURE_SSL_REDIRECT = True SESSION_COOKIE_SECURE = True SESSION_COOKIE_HTTPONLY = True SESSION_COOKIE_SAMESITE = "Lax" CSRF_COOKIE_SECURE = True SECURE_CONTENT_TYPE_NOSNIFF = True SECURE_REFERRER_POLICY = "same-origin" SECURE_CROSS_ORIGIN_OPENER_POLICY = "same-origin" X_FRAME_OPTIONS = "DENY"

ALLOWED_HOSTS защищает обработку Host header, если код использует request.get_host(). Не читайте необработанный HTTP_HOST и не используйте "*" без собственной строгой валидации.

CSRF_TRUSTED_ORIGINS — список origins, которым разрешено присылать небезопасные запросы с точки зрения CSRF. Он не настраивает CORS и не делает origin «доверенным» для чтения ответов браузером.

#HTTPS за reverse proxy

Если TLS завершается на доверенном proxy, Django может видеть внутренний HTTP. Только когда proxy удаляет входной заголовок клиента и сам выставляет корректное значение, настройте:

SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")

Ошибочная доверенность к X-Forwarded-Proto влияет на request.is_secure(), HTTPS redirect и CSRF. Проверяйте всю цепочку CDN/load balancer/proxy, а не копируйте настройку вслепую.

#HSTS вводят постепенно

HSTS сообщает браузеру, что домен нужно открывать только по HTTPS. Ошибка может сделать сайт недоступным на весь заданный срок.

Начните с небольшого значения и наблюдения:

SECURE_HSTS_SECONDS = 300

После проверки всех маршрутов увеличивайте срок. SECURE_HSTS_INCLUDE_SUBDOMAINS = True безопасен только тогда, когда каждый поддомен поддерживает HTTPS. SECURE_HSTS_PRELOAD = True лишь добавляет директиву; включение домена в browser preload list — отдельная внешняя и трудно обратимая процедура.

#CSRF: защита действий, а не только форм

Первое правило — GET, HEAD и другие safe methods не изменяют состояние. Тогда CsrfViewMiddleware защищает небезопасные запросы с cookie-аутентификацией.

<form method="post"> {% csrf_token %} {{ form.as_div }} <button type="submit">Сохранить</button> </form>

Для JavaScript-клиента токен передают в X-CSRFToken. Не ставьте @csrf_exempt на endpoint только потому, что он возвращает JSON: формат ответа не меняет модель атаки. Bearer credential из заголовка, который браузер не добавляет автоматически, обычно не создаёт ту же CSRF-модель; JWT в cookie снова требует защиты.

SameSite и проверка Origin/Referer дополняют CSRF token, но не оправдывают изменение данных через GET.

#XSS и контексты вывода

Django Template Language экранирует переменные в HTML text/attribute context:

<p>{{ comment.text }}</p>

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

  • |safe, mark_safe() и {% autoescape off %};
  • вставка данных в JavaScript, CSS или URL без подходящего способа кодирования;
  • пользовательский HTML из rich-text редактора;
  • DOM XSS в frontend-коде после получения безопасного JSON.

Для передачи данных в script используйте json_script, а затем разбирайте JSON:

{{ chart_data|json_script:"chart-data" }} <script nonce="{{ csp_nonce }}"> const chartData = JSON.parse( document.getElementById("chart-data").textContent ); </script>

Если продукт действительно принимает пользовательский HTML, очищайте его серверной allowlist-библиотекой, поддерживайте эту библиотеку в актуальном состоянии и отдельно проверяйте ссылки/атрибуты. «Мы доверяем автору» не защищает от украденной учётной записи.

HttpOnly мешает JavaScript прочитать session cookie, но XSS всё ещё может отправлять запросы от имени пользователя. Поэтому устранение XSS и CSP важнее надежды на один cookie-флаг.

#SQL injection и динамические идентификаторы

ORM параметризует значения:

User.objects.filter(username=user_input)

Raw SQL тоже безопасен при placeholders для значений:

with connection.cursor() as cursor: cursor.execute( "SELECT id, username FROM accounts_user WHERE id = %s", [user_id], )

Нельзя параметризовать имя столбца тем же способом. Если клиент выбирает сортировку, сопоставьте публичное значение с известным ORM-полем:

ORDERING = { "newest": "-created_at", "title": "title", } ordering = ORDERING.get(request.GET.get("sort"), "-created_at") queryset = Post.objects.order_by(ordering)

Не собирайте SQL через f-string, %, .format() или конкатенацию. Особого внимания требуют RawSQL, extra(), собственные database functions и параметры where/table сторонних библиотек.

#Секреты и ротация

Обращение os.environ["..."] заставляет приложение завершиться при отсутствии секрета. os.environ.get() без проверки может тихо передать None или небезопасный fallback.

Храните production-секреты в platform secret store, Vault/KMS или защищённых переменных окружения. .env удобен локально, но должен быть исключён из Git; .env.example содержит только имена и фиктивные значения.

Разделяйте ключи по окружениям и назначению. При ротации Django SECRET_KEY можно временно читать старые подписи через SECRET_KEY_FALLBACKS, оставляя новый ключ основным. Fallback увеличивает поверхность проверки, поэтому старый ключ удаляют после разумного периода.

Если секрет уже попал в Git, удаления из последнего commit недостаточно: считайте его скомпрометированным, отзовите/смените и проверьте историю и логи.

Не пишите в логи пароли, session IDs, CSRF tokens, Authorization, полные webhook payload с персональными данными и строки подключения. Настройте фильтрацию как в приложении, так и в proxy/APM.

#Сессии и cookie

Secure разрешает отправку cookie только по HTTPS. HttpOnly запрещает чтение из JavaScript. SameSite ограничивает часть cross-site отправок. У каждого флага своя задача; ни один не шифрует значение и не заменяет TLS.

CSRF_COOKIE_HTTPONLY=True не даёт значимого общего выигрыша от XSS: CSRF token присутствует в DOM формы, а XSS уже может выполнять действия. Кроме того, JavaScript-клиенту тогда приходится получать токен из DOM. Включайте настройку только с понятным клиентским flow.

После login Django меняет session key для защиты от session fixation. Для критичных операций полезны повторная аутентификация, ограничение возраста сессии и возможность завершить активные сессии пользователя.

#Загружаемые файлы

Проверка расширения — только первый фильтр. Имя .jpg не доказывает, что содержимое является изображением, а MIME клиента недостоверен.

Практический pipeline:

  1. proxy ограничивает размер request body до передачи Django;
  2. приложение ограничивает число файлов, заявленный размер и allowlist форматов;
  3. формат декодируется специализированной актуальной библиотекой;
  4. сервер генерирует случайное имя и не доверяет пути клиента;
  5. объект сначала попадает в quarantine и при необходимости сканируется;
  6. только проверенный объект становится доступным пользователю.

DATA_UPLOAD_MAX_MEMORY_SIZE не ограничивает общий размер multipart-файлов так, как часто предполагают. Django прямо рекомендует ограничить body на production web server; под ASGI запрос может успеть попасть во временный файл до прикладной проверки.

Пользовательские файлы лучше отдавать с отдельного origin без application cookies и без возможности выполнения HTML/JS в контексте основного домена. Для скачивания неизвестных типов используйте Content-Disposition: attachment и X-Content-Type-Options: nosniff. Приватный storage не должен становиться публичным только из-за знания URL; выдавайте короткоживущую ссылку после permission-проверки.

Современная конфигурация storage:

STORAGES = { "default": { "BACKEND": "myproject.storage.PrivateMediaStorage", }, "staticfiles": { "BACKEND": ( "django.contrib.staticfiles.storage.ManifestStaticFilesStorage" ), }, }

Удалено в Django 5.1: настройки DEFAULT_FILE_STORAGE и STATICFILES_STORAGE. Их заменила настройка STORAGES. Старые имена могут встретиться в legacy-проектах на Django 4.2/5.0.

#Content Security Policy

#Новое в Django 6.0

Django 6.0 умеет формировать CSP без стороннего middleware:

from django.utils.csp import CSP MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", "django.middleware.csp.ContentSecurityPolicyMiddleware", # ... ] SECURE_CSP_REPORT_ONLY = { "default-src": [CSP.SELF], "script-src": [CSP.SELF, CSP.NONCE], "object-src": [CSP.NONE], "base-uri": [CSP.SELF], "frame-ancestors": [CSP.NONE], "report-uri": "/csp-report/", }

Сначала соберите отчёты, устраните inline-скрипты и ложные нарушения, затем перенесите проверенную политику в SECURE_CSP. Report-only ничего не блокирует.

Для nonce добавьте django.template.context_processors.csp и используйте переменную csp_nonce только в тех шаблонах, где nonce действительно нужен. HTML с nonce и соответствующий CSP header должны быть сформированы одним запросом: готовую страницу нельзя независимо от заголовка раздавать из общего кэша. Не добавляйте 'unsafe-inline' ради быстрого исчезновения ошибок: это заметно ослабляет защиту script policy.

CSP уменьшает последствия многих XSS, но не исправляет небезопасный HTML и не заменяет escaping. frame-ancestors — современная защита от framing; X_FRAME_OPTIONS полезен как совместимый дополнительный слой.

Для Django 5.2 применяют заголовок на reverse proxy или актуальный django-csp. Его старые имена настроек не следует смешивать с SECURE_CSP Django 6.0.

#Admin и authentication

Случайный URL admin уменьшает фоновый шум сканеров, но не является контролем доступа. Надёжные меры:

  • MFA для staff;
  • rate limiting/защита login и наблюдение за неудачами;
  • отдельные staff-аккаунты без повседневного использования superuser;
  • ограничение сети/VPN там, где это допустимо;
  • короткие сессии и повторная проверка критичных действий;
  • минимальные permissions и аудит изменений.

Сторонние пакеты вроде django-axes и MFA-решений оценивайте по совместимости с выбранной версией Django, proxy-схемой и политикой блокировки. Блокировка только по IP может устроить denial of service пользователям за общим NAT.

#CORS, SSRF и исходящие запросы

CORS управляет тем, какие браузерные origins могут читать ответ. Он не аутентифицирует запрос, не заменяет CSRF и не защищает server-to-server API. Не сочетайте отражаемый любой Origin с credentials.

Если пользователь задаёт URL для импорта, webhook-проверки или превью, появляется SSRF. Используйте allowlist назначения, запрещайте private/link-local/metadata ranges после DNS resolution, повторяйте проверку на redirect, ограничивайте протоколы, размер, время и число переходов. Лучше выполнять такие запросы из изолированной сети без доступа к внутренним сервисам.

#Handoff checklist

  • поддерживаемые patch-релизы и регулярный dependency audit;
  • DEBUG=False, точный ALLOWED_HOSTS, секреты вне Git;
  • проверенная proxy-схема, HTTPS, cookies и постепенный HSTS;
  • safe HTTP methods без побочных эффектов, CSRF не отключён по привычке;
  • нет необоснованных safe, raw SQL и динамических identifiers;
  • scoped querysets и permissions проверены для каждого tenant;
  • upload ограничен до Django и изолирован на storage/origin;
  • CSP сначала протестирован в report-only, затем enforced;
  • логи очищены от credentials и персональных payload;
  • есть мониторинг authentication anomalies, permission denials и security headers.

#Legacy: что удалено или переосмыслено

  • Удалено в Django 4.0: SECURE_BROWSER_XSS_FILTER; SecurityMiddleware больше не устанавливает X-XSS-Protection. Используйте CSP без небезопасного inline-кода.
  • Удалено в Django 5.1: DEFAULT_FILE_STORAGE и STATICFILES_STORAGE; используйте STORAGES.
  • Смена /admin/ на секретный путь не считается защитой от brute force.
  • Проверка только расширения и переданного MIME не делает upload безопасным.
  • CSRF_TRUSTED_ORIGINS не является настройкой CORS и не должно содержать домены «на всякий случай».

#Официальная документация

  • Django: security
  • Django: deployment checklist
  • Django 6.0: CSP
  • Django: security settings

Далее: Асинхронный Django