Код-ревью Middle Безопасность веб-приложения спрашивают часто обновлено

Код-ревью: поиск, редирект и импорт настроек

Вопрос

Найдите уязвимости в эндпоинтах Django.

import pickle
import requests
from django.db import connection
from django.http import HttpResponse, JsonResponse
from django.shortcuts import redirect
from django.utils.safestring import mark_safe


def search(request):
    q = request.GET.get('q', '')
    with connection.cursor() as cur:
        cur.execute(f"SELECT id, title FROM posts WHERE title LIKE '%{q}%'")
        rows = cur.fetchall()
    html = '<h1>Результаты для ' + q + '</h1>'
    return HttpResponse(mark_safe(html))


def go(request):
    return redirect(request.GET['next'])


def import_settings(request):
    data = pickle.loads(request.body)
    return JsonResponse({'ok': True, 'keys': list(data)})


def preview(request):
    r = requests.get(request.GET['url'])
    return HttpResponse(r.content[:500])

Коротко

Исправления: Для превью ссылок: только http(s), резолв и запрет внутренних адресов, проверка редиректов, таймаут и лимит размера, а лучше отдельный изолированный сервис. Плюс все эти эндпоинты должны требовать вход, если они не публичные.

Что должен найти ревьюер

  1. Безопасностьстрока 12
    SQL-инъекция: ввод вставлен в SQL f-строкой; параметр %s со значением f"%{q}%"
  2. Безопасностьстрока 14
    Отражённый XSS: ввод склеен в HTML и помечен безопасным; использовать шаблон с автоэкранированием или escape
  3. Безопасностьстрока 19
    Открытый редирект на любой адрес; проверять url_has_allowed_host_and_scheme
  4. Безопасностьстрока 23
    Десериализация pickle из запроса: выполнение произвольного кода; принимать JSON
  5. Безопасностьстрока 28
    SSRF: сервер ходит по любому URL, включая внутренние адреса, без таймаута и лимита размера

Разбор

Исправления:

def search(request):
    q = request.GET.get('q', '')
    posts = Post.objects.filter(title__icontains=q).values('id', 'title')[:50]
    return render(request, 'search.html', {'q': q, 'posts': posts})


def go(request):
    nxt = request.GET.get('next', '/')
    if not url_has_allowed_host_and_scheme(nxt, allowed_hosts={request.get_host()}, require_https=True):
        nxt = '/'
    return redirect(nxt)


def import_settings(request):
    data = json.loads(request.body)
    ...

Для превью ссылок: только http(s), резолв и запрет внутренних адресов, проверка редиректов, таймаут и лимит размера, а лучше отдельный изолированный сервис. Плюс все эти эндпоинты должны требовать вход, если они не публичные.

Ответьте сами, а не прочитайте

В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.

Термины по теме