Статья по безопасности Django (Django Security Cheat Sheet)

Введение

Django — мощный веб-фреймворк на Python; в нём уже есть средства защиты от типичных уязвимостей, которые можно использовать «из коробки». В этой статье перечислены действия и советы по разработке более безопасных приложений на Django: она охватывает распространённые риски и помогает усилить защиту. У каждого пункта — краткое пояснение и примеры кода в контексте Django.

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

Общие рекомендации

  • Регулярно обновляйте Django и зависимости проекта, чтобы получать исправления уязвимостей.
  • В продакшене приложение не должно работать в режиме DEBUG. Никогда не используйте DEBUG = True в бою.
  • Подключайте пакеты вроде django_ratelimit или django-axes для ограничения частоты запросов и защиты от перебора паролей.

Аутентификация

  • Для входа, выхода, смены пароля и т.п. используйте приложение django.contrib.auth и его формы/представления. Подключите модуль и зависимости django.contrib.contenttypes и django.contrib.sessions в INSTALLED_APPS в settings.py.
INSTALLED_APPS = [
    # ...
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    # ...
]
  • Декоратор @login_required ограничивает доступ к представлению только аутентифицированным пользователям. Ниже — типичные варианты.
from django.contrib.auth.decorators import login_required

# Неаутентифицированный пользователь перенаправляется на страницу входа по умолчанию.
@login_required
def my_view(request):
    # логика представления
    pass

# Перенаправление на свой URL входа.
@login_required(login_url='/login-page/')
def my_view(request):
    # логика представления
    pass
  • Задайте политику паролей через валидаторы: настройте AUTH_PASSWORD_VALIDATORS в settings.py под требования приложения.
AUTH_PASSWORD_VALIDATORS = [
  {
    # Сходство пароля с атрибутами пользователя.
    'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    'OPTIONS': {
      'user_attributes': ('username', 'email', 'first_name', 'last_name'),
      'max_similarity': 0.7,
    }
  },
  {
    # Минимальная длина пароля.
    'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    'OPTIONS': {
      'min_length': 8,
    }
  },
  {
    # Проверка по списку распространённых паролей.
    'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
  },
  {
    # Запрет полностью числового пароля.
    'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
  }
]
  • Для хеширования пароля в виде открытого текста используйте утилиту make_password.
from django.contrib.auth.hashers import make_password
# ...
hashed_pwd = make_password('plaintext_password')
  • Проверку открытого пароля на соответствие хешу выполняйте через check_password.
from django.contrib.auth.hashers import check_password
# ...
plain_pwd = 'plaintext_password'
hashed_pwd = 'hashed_password_from_database'

if check_password(plain_pwd, hashed_pwd):
    print("The password is correct.")
else:
    print("The password is incorrect.")

Управление ключами

Параметр SECRET_KEY в settings.py используется для криптографической подписи и должен храниться в секрете. Рекомендации:

  • Генерируйте ключ длиной не менее 50 символов из букв, цифр и символов.
  • Используйте криптостойкий генератор, например get_random_secret_key() в Django.
  • Не зашивайте SECRET_KEY в репозиторий. Храните значение в переменных окружения или в менеджере секретов.
import os
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
  • Планируйте ротацию ключа с учётом того, что это инвалидирует сессии, токены сброса пароля и т.д. При компрометации ключ смените немедленно.

Заголовки HTTP

Включите django.middleware.security.SecurityMiddleware в списке MIDDLEWARE в settings.py, чтобы добавлять к ответам заголовки, связанные с безопасностью. Среди прочего настраиваются:

  • SECURE_CONTENT_TYPE_NOSNIFF: установите True, чтобы включить X-Content-Type-Options: nosniff и снизить риск подмены MIME.
  • SECURE_HSTS_SECONDS: задаёт политику HSTS (доступ к сайту только по HTTPS).

Подключите django.middleware.clickjacking.XFrameOptionsMiddleware в MIDDLEWARE (после SecurityMiddleware — порядок важен) для параметров вроде:

  • X_FRAME_OPTIONS: значение DENY или SAMEORIGIN добавляет заголовок X-Frame-Options ко всем ответам и помогает против clickjacking.

Content Security Policy (CSP)

Встроенной поддержки CSP в Django по умолчанию нет. Реализуйте политику через сторонние библиотеки (например, django-csp) или через заголовки HTTP.

Подробнее:

Файлы cookie

  • SESSION_COOKIE_SECURE = True в settings.py — передавать cookie сессии только по HTTPS.
  • CSRF_COOKIE_SECURE = True — то же для CSRF-cookie.
  • При установке своих cookie через HttpResponse.set_cookie() задавайте secure=True.
response = HttpResponse("Some response")
response.set_cookie('my_cookie', 'cookie_value', secure=True)

Межсайтовая подделка запроса (CSRF)

  • Включите django.middleware.csrf.CsrfViewMiddleware в MIDDLEWARE в settings.py.
  • В формах используйте тег шаблона {% csrf_token %}, как в примере ниже.
<form method="post">
    {% csrf_token %}
    <!-- поля формы -->
</form>

См. также OWASP: предотвращение CSRF.

Межсайтовый скриптинг (XSS)

Рекомендации этого раздела дополняют общие меры против XSS.

  • Используйте встроенный шаблонизатор Django. Автоэкранирование HTML описано в документации.
  • По возможности не отключайте экранирование фильтром safe и функцией mark_safe. Если без этого нельзя, убедитесь, что данные из доверенного источника; к пользовательскому вводу относитесь особенно осторожно.
  • Для передачи данных в JavaScript из шаблонов используйте фильтр json_script.
  • Подробнее — раздел Django про защиту от XSS.

См. OWASP: предотвращение XSS.

HTTPS

  • Убедитесь, что в MIDDLEWARE есть django.middleware.security.SecurityMiddleware.
  • SECURE_SSL_REDIRECT = True в settings.py перенаправляет HTTP на HTTPS (301).
  • За прокси или балансировщиком настройте SECURE_PROXY_SSL_HEADER, чтобы Django корректно определял схему исходного запроса. См. документацию.

URL панели администратора

Имеет смысл изменить стандартный путь к админке (example.com/admin/), чтобы усложнить автоматизированные атаки. В корневом urls.py проекта измените элемент в urlpatterns, ведущий на admin.site.urls, на путь, отличный от admin/.

Встроенная команда Django check --deploy

Для проверок перед выкладкой используйте check --deploy. Пример вывода:

$ ./manage.py check --deploy
System check identified some issues:

WARNINGS:
?: (security.W004) You have not set a value for the SECURE_HSTS_SECONDS setting. If your entire site is served only over SSL, you may want to consider setting a value and enabling HTTP Strict Transport Security. Be sure to read the documentation first; enabling HSTS carelessly can cause serious, irreversible problems.
?: (security.W008) Your SECURE_SSL_REDIRECT setting is not set to True. Unless your site should be available over both SSL and non-SSL connections, you may want to either set this setting True or configure a load balancer or reverse-proxy server to redirect all connections to HTTPS.
?: (security.W009) Your SECRET_KEY has less than 50 characters, less than 5 unique characters, or it's prefixed with 'django-insecure-' indicating that it was generated automatically by Django. Please generate a long and random value, otherwise many of Django's security-critical features will be vulnerable to attack.
?: (security.W012) SESSION_COOKIE_SECURE is not set to True. Using a secure-only session cookie makes it more difficult for network traffic sniffers to hijack user sessions.
?: (security.W016) You have 'django.middleware.csrf.CsrfViewMiddleware' in your MIDDLEWARE, but you have not set CSRF_COOKIE_SECURE to True. Using a secure-only CSRF cookie makes it more difficult for network traffic sniffers to steal the CSRF token.
?: (security.W018) You should not have DEBUG set to True in deployment.
?: (security.W020) ALLOWED_HOSTS must not be empty in deployment.

System check identified 7 issues (0 silenced).

Устранение предупреждений этой команды — практичный шаг к более жёсткой конфигурации в продакшене.

Ссылки

Дополнительная документация:

© Перевод на русский язык. Оригинальные материалы: OWASP Cheat Sheet Series.
Этот проект использует материалы OWASP, распространяемые по лицензии CC BY-SA 4.0.