security: закрыть уязвимости аудита

- Secure-куки (SESSION/CSRF_COOKIE_SECURE), HSTS, nosniff, Referrer-Policy, X-Frame-Options
- SECURE_PROXY_SSL_HEADER для корректной работы за HTTPS-прокси
- антиспам: не более 3 заявок с одного IP за 10 минут (кэш)
- удалён мусорный main.py (заглушка PyCharm)
- .env.example: DJANGO_DEBUG=0, ALLOWED_HOSTS под прод, COOKIE_SECURE/HSTS включены
This commit is contained in:
2026-08-06 16:14:09 +04:00
parent a01a0da25b
commit 22102dbb7a
4 changed files with 69 additions and 20 deletions
+8 -4
View File
@@ -5,8 +5,12 @@
# Сгенерировать: python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"
DJANGO_SECRET_KEY=
# Включить режим отладки (ТОЛЬКО для разработки!): 1 = да, пусто/0 = нет
DJANGO_DEBUG=1
# Режим отладки: для продакшена строго 0 (пусто). 1 — только локальная разработка!
DJANGO_DEBUG=0
# Разрешённые хосты через запятую (для продакшена: ваш домен)
DJANGO_ALLOWED_HOSTS=127.0.0.1,localhost
# Разрешённые хосты через запятую (для продакшена: ваш домен, например teacher.labnethub.ru)
DJANGO_ALLOWED_HOSTS=teacher.labnethub.ru,127.0.0.1,localhost
# Secure-куки и HSTS (сайт за HTTPS-прокси): держать включёнными (=1)
DJANGO_COOKIE_SECURE=1
DJANGO_HSTS_SECONDS=31536000
+27
View File
@@ -50,10 +50,37 @@ MIDDLEWARE = [
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
# --- HTTPS / безопасность кук (сайт работает только через HTTPS-прокси) ---
# nginx должен передавать заголовок X-Forwarded-Proto: https
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
SESSION_COOKIE_SECURE = os.environ.get("DJANGO_COOKIE_SECURE", "1") == "1"
CSRF_COOKIE_SECURE = os.environ.get("DJANGO_COOKIE_SECURE", "1") == "1"
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = "Lax"
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_REFERRER_POLICY = "strict-origin-when-cross-origin"
X_FRAME_OPTIONS = "DENY"
SECURE_HSTS_SECONDS = int(os.environ.get("DJANGO_HSTS_SECONDS", "31536000"))
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
# Редирект на HTTPS не включаем — его делает nginx (иначе цикл при неправильном X-Forwarded-Proto)
# SECURE_SSL_REDIRECT = True
# --- django-axes: защита от брутфорса логина ---
AXES_FAILURE_LIMIT = 5
AXES_COOLOFF_TIME = 1 # часы
AXES_RESET_ON_SUCCESS = True
AXES_LOCKOUT_PARAMETERS = ["ip_address"]
# --- антиспам: лимит заявок с одного IP ---
# Кэш in-memory (locmem) подходит для одного воркера; для нескольких воркеров
# нужно memcached/redis, иначе лимит считается на каждый процесс отдельно.
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
"LOCATION": "teacher-site-cache",
}
}
ROOT_URLCONF = "config.urls"
+34
View File
@@ -1,14 +1,48 @@
from django.contrib import messages
from django.core.cache import cache
from django.db import IntegrityError, transaction
from django.shortcuts import redirect, render
from django.urls import reverse
from django.utils import timezone
from .forms import ConsultationRequestForm
from .models import ConsultationSlot
# Антиспам: не более 3 заявок с одного IP за 10 минут
SUBMIT_LIMIT = 3
SUBMIT_WINDOW_SECONDS = 600
def _client_ip(request):
# X-Forwarded-For добавляет nginx; берём первый адрес (реальный клиент)
xff = request.META.get("HTTP_X_FORWARDED_FOR")
if xff:
return xff.split(",")[0].strip()
return request.META.get("REMOTE_ADDR", "unknown")
def _submit_allowed(request):
key = f"submit:{_client_ip(request)}"
now = timezone.now().timestamp()
stamps = cache.get(key, [])
stamps = [s for s in stamps if now - s < SUBMIT_WINDOW_SECONDS]
if len(stamps) >= SUBMIT_LIMIT:
cache.set(key, stamps, SUBMIT_WINDOW_SECONDS)
return False
stamps.append(now)
cache.set(key, stamps, SUBMIT_WINDOW_SECONDS)
return True
def home(request):
if request.method == "POST":
if not _submit_allowed(request):
messages.error(
request,
"Слишком много заявок за короткое время. Попробуйте через 10 минут.",
)
return redirect(f"{reverse('consultations:home')}#booking")
form = ConsultationRequestForm(request.POST)
if form.is_valid():
try:
-16
View File
@@ -1,16 +0,0 @@
# This is a sample Python script.
# Press Shift+F10 to execute it or replace it with your code.
# Press Double Shift to search everywhere for classes, files, tool windows, actions, and settings.
def print_hi(name):
# Use a breakpoint in the code line below to debug your script.
print(f'Hi, {name}') # Press Ctrl+F8 to toggle the breakpoint.
# Press the green button in the gutter to run the script.
if __name__ == '__main__':
print_hi('PyCharm')
# See PyCharm help at https://www.jetbrains.com/help/pycharm/