Типові помилки безпеки — Security Misconceptions¶
Мета: Розвіяти поширені хибні уявлення про безпеку вебзастосунків. Ці помилки роблять НАВІТЬ досвідчені розробники!
Помилка 1: "Я приховав кнопку — значить захистив"¶
Що роблять: Ховають кнопку "Видалити" в шаблоні для юзерів без прав.
<!-- шаблон: ховаємо кнопку — BUT це тільки UX, НЕ безпека! -->
{% if note.user == request.user %}
<a href="/notes/{{ note.pk }}/delete/">Видалити</a>
{% endif %}
Проблема: Атакер відкриває інструменти розробника (F12), бачить ID нотатки і надсилає запит напряму:
# Атакер знає URL: /notes/42/delete/
# Він надсилає POST-запит напряму через curl або Postman:
curl -X POST http://mysite.com/notes/42/delete/ \
-H "Cookie: sessionid=attacker_session"
# Результат: нотатка Боба видалена!
Правильне рішення — захист у VIEW:
# views.py:
@login_required
def note_delete(request, pk):
# Перевірка ЗАВЖДИ у view, не тільки у шаблоні:
note = get_object_or_404(Note, pk=pk, user=request.user)
# ↑ Якщо note.user != request.user → Django поверне 404
if request.method == 'POST':
note.delete()
return redirect('note_list')
Помилка 2: "Якщо юзер залогінений — він може все"¶
Що думають: @login_required захищає від всього.
Проблема: @login_required перевіряє лише факт входу, але НЕ перевіряє право власності на конкретний об'єкт.
# НЕБЕЗПЕЧНО — IDOR (Insecure Direct Object Reference):
@login_required
def note_edit(request, pk):
note = get_object_or_404(Note, pk=pk) # pk=42 належить Бобу
# Аліса залогінена → @login_required OK
# Але Аліса редагує нотатку БОБА!
# БЕЗПЕЧНО — додай user= фільтр:
@login_required
def note_edit(request, pk):
note = get_object_or_404(Note, pk=pk, user=request.user)
# pk=42 належить Бобу, user=Alice → 404 ✓
Правило:
@login_required+get_object_or_404(Model, pk=pk, user=request.user)= правильний захист
Помилка 3: "Суперюзер — це нормальний адмін"¶
Що роблять: Тестують весь функціонал від суперюзера.
Проблема:
# Суперюзер:
user.has_perm('notes_app.delete_note') # True — ЗАВЖДИ, навіть без явного права
# Звичайний staff-юзер:
user.has_perm('notes_app.delete_note') # False — якщо право не призначено
# Якщо ти тестуєш від superuser і бачиш що все ОК →
# НЕ ФАКТ, що звичайний юзер теж матиме доступ!
Правило:
Тестуй кожну роль з правильним типом акаунту: звичайний юзер, staff, superuser — окремо.
Помилка 4: "Глобальний queryset — це нормально"¶
Що роблять: Note.objects.get(id=note_id) без фільтру по юзеру.
# НЕБЕЗПЕЧНО — Аліса може отримати будь-яку нотатку:
note = Note.objects.get(id=note_id)
# Атакер перебирає ID: /notes/1/, /notes/2/, /notes/3/...
# → знаходить конфіденційні нотатки інших юзерів!
# БЕЗПЕЧНО:
note = get_object_or_404(Note, id=note_id, user=request.user)
# або
note = Note.objects.get(id=note_id, user=request.user)
Помилка 5: "Client-side валідація захищає"¶
Що роблять: Перевіряють дані тільки в HTML-формі або JavaScript.
<!-- HTML форма: -->
<input type="number" name="priority" min="1" max="4">
<!-- Атакер відкриває F12 → змінює min/max → надсилає priority=999 -->
Правило:
Клієнтська валідація — тільки для UX (зручність). Серверна валідація — обов'язкова для безпеки.
# Django ModelForm — server-side валідація:
class NoteForm(forms.ModelForm):
class Meta:
model = Note
fields = ['title', 'content', 'priority']
def clean_priority(self):
priority = self.cleaned_data['priority']
if priority not in [1, 2, 3, 4]:
raise forms.ValidationError("Невірний пріоритет")
return priority
Помилка 6: "DEBUG=True в production — тимчасово"¶
Що роблять: Залишають DEBUG=True на сервері "поки не знайдуть баг".
Що отримує атакер від DEBUG=True:
- Повний стектрейс помилки (назви файлів, рядки коду)
- Значення всіх локальних змінних у момент помилки
- SECRET_KEY (витік = повний злам сесій!)
- Всі SQL-запити зберігаються у пам'яті → memory leak!
Правило:
DEBUG = Falseзавжди в production. Без виключень. Для відлагодження в production — використовуй логи (logging).
Підсумок: Чек-ліст безпеки View¶
Перед кожним новим view перевіряй:
@login_required # ✓ чи залогінений?
def my_view(request, pk):
obj = get_object_or_404(Model, pk=pk, # ✓ чи є право на цей об'єкт?
user=request.user)
if request.method == 'POST':
form = MyForm(request.POST) # ✓ форма з POST-даними
if form.is_valid(): # ✓ серверна валідація
# ... логіка
# Шаблон: ховай кнопки для UX, але захист у view ✓
return render(request, 'template.html', {'obj': obj})