CSRF, XSS, SQLi, clickjacking, security headers, secrets management
Безопасность — не опция, а обязательное требование для production приложений. Этот туториал охватывает все аспекты защиты Django приложения.
💡 Правило: Безопасность должна быть многослойной (defense in depth). Не полагайтесь на одну защиту.
Атака: Злоумышленник заставляет браузер пользователя выполнить действие на доверенном сайте.
Защита в Django:
# settings.py
MIDDLEWARE = [
'django.middleware.csrf.CsrfViewMiddleware', # Обязательно!
]
# В шаблонах
<form method="post">
{% csrf_token %}
<!-- поля -->
</form>
# Для API (если нужно отключить)
from django.views.decorators.csrf import csrf_exempt, ensure_csrf_cookie
@csrf_exempt # Не рекомендуется для форм
def api_endpoint(request):
pass
# Для доверенных доменов
CSRF_TRUSTED_ORIGINS = [
'https://example.com',
'https://api.example.com',
]Атака: Внедрение вредоносного JavaScript в страницы сайта.
Защита в Django:
# Автоматическое экранирование
{{ user_input }} # <script> → <script>
# Явное экранирование
{{ user_input|escape }}
# Отключение (ОПАСНО!)
{{ user_input|safe }} # Только если доверяете контенту!
{% autoescape off %}{{ user_input }}{% endautoescape %}
# Для HTML контента от пользователей
# Используйте bleach для санитизации
import bleach
clean_html = bleach.clean(dirty_html, tags=['p', 'br', 'strong'])Атака: Внедрение вредоносного SQL через пользовательский ввод.
Защита в Django:
# ✅ Безопасно (ORM)
User.objects.filter(username=username)
# ✅ Безопасно (параметризованный raw SQL)
cursor.execute('SELECT * FROM users WHERE id = %s', [id])
# ❌ ОПАСНО! SQL Injection!
cursor.execute(f"SELECT * FROM users WHERE id = {id}")
cursor.execute("SELECT * FROM users WHERE id = " + id)
# ❌ ОПАСНО! Даже с format()
query = "SELECT * FROM users WHERE id = {}".format(id)
cursor.execute(query)# settings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', # ПЕРВЫМ!
# ... другие middleware
]
# HTTPS редирект
SECURE_SSL_REDIRECT = True
# HSTS (HTTP Strict Transport Security)
SECURE_HSTS_SECONDS = 31536000 # 1 год
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True # Для preload списка браузеров
# Защита от MIME sniffing
SECURE_CONTENT_TYPE_NOSNIFF = True # X-Content-Type-Options: nosniff
# XSS Protection (устарело, браузеры сами защищают)
SECURE_BROWSER_XSS_FILTER = True # X-XSS-Protection: 1
# Clickjacking защита
X_FRAME_OPTIONS = 'DENY' # или 'SAMEORIGIN'
# Referrer Policy
SECURE_REFERRER_POLICY = 'strict-origin-when-cross-origin'# settings.py
# Только HTTPS
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
# Недоступен из JavaScript (защита от XSS)
SESSION_COOKIE_HTTPONLY = True
CSRF_COOKIE_HTTPONLY = True
# SameSite защита от CSRF
SESSION_COOKIE_SAMESITE = 'Lax' # или 'Strict'
# Имя cookie (усложняет атаку)
SESSION_COOKIE_NAME = 'sessionid_custom'
CSRF_COOKIE_NAME = 'csrftoken_custom'
# Время жизни сессии
SESSION_COOKIE_AGE = 1209600 # 2 недели (по умолчанию)
SESSION_EXPIRE_AT_BROWSER_CLOSE = True # Истекать при закрытии браузера
# Вращение сессии
SESSION_SAVE_EVERY_REQUEST = True # Обновлять expiry при каждом запросе# ❌ ОПАСНО! В settings.py
SECRET_KEY = 'django-insecure-abc123...'
DATABASE_PASSWORD = 'supersecret'
# ✅ Безопасно
import os
from pathlib import Path
# Из переменных окружения
SECRET_KEY = os.environ.get('SECRET_KEY')
DATABASE_PASSWORD = os.environ.get('DATABASE_PASSWORD')
# Из .env файла (не коммитьте!)
from dotenv import load_dotenv
load_dotenv(Path('.env'))
SECRET_KEY = os.environ.get('SECRET_KEY')
# Для production используйте secrets manager
# AWS Secrets Manager
import boto3
client = boto3.client('secretsmanager')
response = client.get_secret_value(SecretId='myapp/prod')
secrets = json.loads(response['SecretString'])
SECRET_KEY = secrets['SECRET_KEY']
# HashiCorp Vault
import hvac
client = hvac.Client(url='http://vault:8200', token=os.environ.get('VAULT_TOKEN'))
secret = client.secrets.kv.v2.read_secret_version(path='myapp/prod')
SECRET_KEY = secret['data']['data']['SECRET_KEY']# .env (добавьте в .gitignore!)
SECRET_KEY=your-secret-key-here
DATABASE_PASSWORD=supersecret
DEBUG=False
ALLOWED_HOSTS=example.com,www.example.com# .gitignore
.env
.env.*
!.env.example
# urls.py
from django.contrib import admin
# ❌ Небезопасно
# path('admin/', admin.site.urls)
# ✅ Безопаснее
path('x7k9m2-admin-portal/', admin.site.urls)
# Или через переменную окружения
import os
ADMIN_URL = os.environ.get('ADMIN_URL', 'admin/')
path(ADMIN_URL, admin.site.urls)pip install django-axes# settings.py
INSTALLED_APPS = [
# ...
'axes',
]
MIDDLEWARE = [
# ...
'axes.middleware.AxesMiddleware', # После AuthenticationMiddleware
]
AXES_ENABLED = True
AXES_FAILURE_LIMIT = 5 # Блокировка после 5 попыток
AXES_COOLOFF_TIME = 1 # Час блокировки
AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP = True
# Логирование попыток
AXES_HANDLER = 'axes.handlers.database.AxesDatabaseHandler'pip install django-two-factor-auth# settings.py
INSTALLED_APPS = [
# ...
'django_otp',
'django_otp.plugins.otp_totp',
'django_otp.plugins.otp_static',
'two_factor',
]
LOGIN_URL = 'two_factor:login'
LOGIN_REDIRECT_URL = 'two_factor:profile'# validators.py
import os
from django.core.exceptions import ValidationError
def validate_file_extension(value):
"""Проверка расширения файла."""
ext = os.path.splitext(value.name)[1]
valid_extensions = ['.pdf', '.doc', '.docx', '.jpg', '.jpeg', '.png']
if not ext.lower() in valid_extensions:
raise ValidationError('Unsupported file extension.')
def validate_file_size(value):
"""Проверка размера файла (макс 5MB)."""
filesize = value.size
max_size = 5 * 1024 * 1024 # 5MB
if filesize > max_size:
raise ValidationError('File too large. Max size is 5MB.')# models.py
from django.db import models
from .validators import validate_file_extension, validate_file_size
import uuid
def upload_to(instance, filename):
"""Безопасный путь загрузки."""
ext = os.path.splitext(filename)[1]
unique_filename = f"{uuid.uuid4()}{ext}"
return f'uploads/{instance.__class__.__name__}/{unique_filename}'
class Document(models.Model):
title = models.CharField(max_length=200)
file = models.FileField(
upload_to=upload_to,
validators=[validate_file_extension, validate_file_size]
)
uploaded_at = models.DateTimeField(auto_now_add=True)
class Meta:
# Хранить метаданные для сканирования
pass# settings.py
# Максимальный размер в памяти
FILE_UPLOAD_MAX_MEMORY_SIZE = 2621440 # 2.5MB
# Максимальный размер запроса
DATA_UPLOAD_MAX_MEMORY_SIZE = 2621440 # 2.5MB
# Хранение вне web root
MEDIA_ROOT = '/var/www/media/' # Не в директории проекта!
MEDIA_URL = '/media/'
# Для S3 хранения
# DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
# AWS_STORAGE_BUCKET_NAME = 'myapp-uploads'
# AWS_S3_CUSTOM_DOMAIN = f'{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com'pip install django-csp# settings.py
INSTALLED_APPS = [
# ...
'csp',
]
MIDDLEWARE = [
# ...
'csp.middleware.CSPMiddleware',
]
# Report only режим (не блокирует, только отчитывает)
CSP_REPORT_ONLY = True
CSP_REPORT_URI = '/csp-report/'
# Источники для скриптов
CSP_SCRIPT_SRC = (
"'self'",
'https://www.google-analytics.com',
)
# Источники для стилей
CSP_STYLE_SRC = (
"'self'",
"'unsafe-inline'", # Избегайте, если возможно
'https://fonts.googleapis.com',
)
# Источники для изображений
CSP_IMG_SRC = (
"'self'",
'data:', # Разрешить data: URL
'https:',
)
# Источники для шрифтов
CSP_FONT_SRC = (
"'self'",
'https://fonts.gstatic.com',
)
# Источники для connect (AJAX, WebSocket)
CSP_CONNECT_SRC = (
"'self'",
'https://api.example.com',
)
# Запрет фреймов (clickjacking защита)
CSP_FRAME_ANCESTORS = ("'none'",)# security_checklist.py
"""Чеклист безопасности для Django production."""
import os
from django.conf import settings
def check_security_settings():
"""Проверить настройки безопасности."""
issues = []
# DEBUG должен быть выключен
if settings.DEBUG:
issues.append('DEBUG должен быть False в production')
# SECRET_KEY из переменных окружения
if settings.SECRET_KEY.startswith('django-insecure'):
issues.append('SECRET_KEY должен быть установлен из переменной окружения')
# HTTPS настройки
if not getattr(settings, 'SECURE_SSL_REDIRECT', False):
issues.append('SECURE_SSL_REDIRECT должен быть True')
if not getattr(settings, 'SECURE_HSTS_SECONDS', 0):
issues.append('SECURE_HSTS_SECONDS должен быть установлен')
# Cookie настройки
if not getattr(settings, 'SESSION_COOKIE_SECURE', False):
issues.append('SESSION_COOKIE_SECURE должен быть True')
if not getattr(settings, 'SESSION_COOKIE_HTTPONLY', False):
issues.append('SESSION_COOKIE_HTTPONLY должен быть True')
# ALLOWED_HOSTS
if not settings.ALLOWED_HOSTS:
issues.append('ALLOWED_HOSTS должен быть установлен')
return issues
# Запуск проверки
issues = check_security_settings()
if issues:
for issue in issues:
print(f'⚠️ {issue}')
else:
print('✅ Все проверки безопасности пройдены')Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.