django-access-review

Revue de sécurité du contrôle d'accès et des IDOR Django. À utiliser lors de la revue de vues Django, de viewsets DRF, de requêtes ORM, ou de tout code Python/Django gérant l'autorisation des utilisateurs. Mots-clés déclencheurs : « IDOR », « contrôle d'accès », « autorisation », « permissions Django », « permissions sur les objets », « isolation des tenants », « accès non autorisé ».

npx skills add https://github.com/getsentry/skills --skill django-access-review

<!-- Matériel de référence basé sur la série OWASP Cheat Sheet (CC BY-SA 4.0) https://cheatsheetseries.owasp.org/ -->

Examen du contrôle d'accès Django et des vulnérabilités IDOR

Trouvez les vulnérabilités de contrôle d'accès en investigant comment la base de code répond à une question :

L'utilisateur A peut-il accéder, modifier ou supprimer les données de l'utilisateur B ?

Philosophie : Investigation plutôt que correspondance de motifs

NE scannez PAS en cherchant des motifs vulnérables prédéfinis. À la place :

  1. Comprenez comment l'autorisation fonctionne dans CETTE base de code
  2. Posez des questions sur les flux de données spécifiques
  3. Tracez le code pour trouver où (ou si) les vérifications d'accès se produisent
  4. Signalez uniquement ce que vous avez confirmé par investigation

Chaque base de code implémente l'autorisation différemment. Votre travail est de comprendre cette implémentation spécifique, puis de trouver les lacunes.


Phase 1 : Comprendre le modèle d'autorisation

Avant de chercher des bugs, répondez à ces questions sur la base de code :

Comment l'autorisation est-elle appliquée ?

Recherchez dans la base de code pour trouver :

□ Où les vérifications de permissions sont-elles implémentées ?
  - Décorateurs ? (@login_required, @permission_required, personnalisés ?)
  - Middleware ? (TenantMiddleware, AuthorizationMiddleware ?)
  - Classes de base ? (BaseAPIView, TenantScopedViewSet ?)
  - Classes de permission ? (DRF permission_classes ?)
  - Mixins personnalisés ? (OwnershipMixin, TenantMixin ?)

□ Comment les requêtes sont-elles limitées en scope ?
  - Managers personnalisés ? (TenantManager, UserScopedManager ?)
  - Surcharges de get_queryset() ?
  - Middleware qui définit le contexte de requête ?

□ Quel est le modèle de propriété ?
  - Propriété d'un seul utilisateur ? (document.owner_id)
  - Propriété organisation/tenant ? (document.organization_id)
  - Hiérarchique ? (org -> team -> user -> resource)
  - Basé sur les rôles dans le contexte ? (administrateur org vs membre)

Commandes d'investigation

# Trouver comment l'auth se fait généralement
grep -rn "permission_classes\|@login_required\|@permission_required" --include="*.py" | head -20

# Trouver les classes de base dont héritent les vues
grep -rn "class Base.*View\|class.*Mixin.*:" --include="*.py" | head -20

# Trouver les managers personnalisés
grep -rn "class.*Manager\|def get_queryset" --include="*.py" | head -20

# Trouver les champs de propriété sur les modèles
grep -rn "owner\|user_id\|organization\|tenant" --include="models.py" | head -30

Ne procédez pas tant que vous ne comprenez pas le modèle d'autorisation.


Phase 2 : Cartographier la surface d'attaque

Identifiez les endpoints qui gèrent les données spécifiques aux utilisateurs :

Quelles ressources existent ?

□ Quels modèles contiennent des données utilisateur ?
□ Lesquels ont des champs de propriété (owner_id, user_id, organization_id) ?
□ Lesquels sont accessibles via un ID dans les URLs ou les corps de requête ?

Quelles opérations sont exposées ?

Pour chaque ressource, cartographiez :

  • Endpoints de liste - quelles données sont retournées ?
  • Endpoints de détail/récupération - comment l'objet est-il récupéré ?
  • Endpoints de création - qui définit le propriétaire ?
  • Endpoints de mise à jour - les utilisateurs peuvent-ils modifier les données d'autrui ?
  • Endpoints de suppression - les utilisateurs peuvent-ils supprimer les données d'autrui ?
  • Actions personnalisées - qu'accèdent-elles ?

Phase 3 : Poser des questions et investiguer

Pour chaque endpoint qui gère les données utilisateur, posez-vous :

La question centrale

« Si je suis l'utilisateur A et je connais l'ID de la ressource de l'utilisateur B, puis-je y accéder ? »

Tracez le code pour répondre :

1. Par où l'ID de la ressource entre-t-il dans le système ?
   - Chemin URL : /api/documents/{id}/
   - Paramètre de requête : ?document_id=123
   - Corps de requête : {"document_id": 123}

2. Où cet ID est-il utilisé pour récupérer les données ?
   - Trouvez la requête ORM ou l'appel base de données

3. Entre (1) et (2), quelles vérifications existent ?
   - La requête est-elle limitée au scope de l'utilisateur actuel ?
   - Y a-t-il une vérification explicite de propriété ?
   - Y a-t-il une vérification de permission sur l'objet ?
   - Une classe de base ou un mixin applique-t-il l'accès ?

4. Si vous ne trouvez pas de vérification, y en a-t-il une que vous avez ratée ?
   - Vérifiez les classes parentes
   - Vérifiez le middleware
   - Vérifiez les managers
   - Vérifiez les décorateurs au niveau de l'URL

Questions de suivi

□ Pour les endpoints de liste : La requête filtre-t-elle pour les données de l'utilisateur,
  ou retourne-t-elle tout ?

□ Pour les endpoints de création : Qui définit le propriétaire - le serveur ou la requête ?

□ Pour les opérations en bloc : Sont-elles limitées au scope des données de l'utilisateur ?

□ Pour les ressources liées : Si je peux accéder à un document, puis-je accéder à ses commentaires ?
  Et si le document appartient à quelqu'un d'autre ?

□ Pour les ressources tenant/org : Un utilisateur de l'org A peut-il accéder aux données
  de l'org B en changeant l'org_id dans l'URL ?

Phase 4 : Tracer les flux spécifiques

Choisissez un endpoint concret et tracez-le complètement.

Exemple d'investigation

Endpoint : GET /api/documents/{pk}/

1. Trouvez la vue qui gère cette URL
   → DocumentViewSet.retrieve() dans api/views.py

2. Vérifiez de quoi DocumentViewSet hérite
   → class DocumentViewSet(viewsets.ModelViewSet)
   → Pas de classe de base personnalisée avec autorisation

3. Vérifiez permission_classes
   → permission_classes = [IsAuthenticated]
   → Vérifie uniquement la connexion, pas la propriété

4. Vérifiez get_queryset()
   → def get_queryset(self):
   →     return Document.objects.all()
   → Retourne TOUS les documents !

5. Vérifiez has_object_permission()
   → Non implémenté

6. Vérifiez la méthode retrieve()
   → Utilise la défaut, qui appelle get_object()
   → get_object() utilise get_queryset(), qui retourne tous

7. Conclusion : IDOR - Tout utilisateur authentifié peut accéder à n'importe quel document

Ce qu'il faut chercher lors du traçage

Indicateurs de lacunes potentielles (investiguer davantage, ne pas auto-signaler) :
- get_queryset() retourne .all() ou filtre sans utilisateur
- Model.objects.get(pk=pk) direct sans propriété dans la requête
- L'ID provient du corps de la requête pour les opérations sensibles
- La classe de permission vérifie l'auth mais pas la propriété
- Pas de has_object_permission() et queryset n'est pas limité en scope

Motifs probablement sûrs (mais vérifiez l'implémentation) :
- get_queryset() filtre par request.user ou l'org de l'utilisateur
- Classe de permission personnalisée avec has_object_permission()
- Classe de base qui applique le scoping
- Manager qui filtre automatiquement

Phase 5 : Signaler les résultats

Signalez uniquement les problèmes que vous avez confirmés par investigation.

Niveaux de confiance

Niveau Signification Action
HIGH Tracé le flux, confirmé qu'aucune vérification n'existe Signaler avec preuves
MEDIUM La vérification peut exister mais n'a pas pu être confirmée Noter pour vérification manuelle
LOW Théorique, probablement atténué Ne pas signaler

Les correctifs suggérés doivent appliquer, non documenter

Mauvais correctif : Ajouter un commentaire disant « l'appelant doit valider les permissions » Bon correctif : Ajouter du code qui valide réellement les permissions

Un commentaire ou une docstring n'applique pas l'autorisation. Votre correctif suggéré doit inclure du code qui :

  • Valide que l'utilisateur a la permission avant de procéder
  • Lève une exception ou retourne une erreur si non autorisé
  • Rend l'accès non autorisé impossible, pas juste découragé

Exemple de suggestion de MAUVAIS correctif :

def get_resource(resource_id):
    # IMPORTANT : L'appelant doit s'assurer que l'utilisateur a accès à cette ressource
    return Resource.objects.get(pk=resource_id)

Exemple de suggestion de BON correctif :

def get_resource(resource_id, user):
    resource = Resource.objects.get(pk=resource_id)
    if resource.owner_id != user.id:
        raise PermissionDenied("Accès refusé")
    return resource

Si vous ne pouvez pas déterminer le bon mécanisme d'application, dites-le - mais ne suggérez jamais la documentation comme correctif.

Format de rapport

## Examen du contrôle d'accès : [Composant]

### Modèle d'autorisation
[Brève description de comment cette base de code gère l'autorisation]

### Résultats

#### [IDOR-001] [Titre] (Sévérité : Haute/Moyenne)
- **Localisation** : `path/to/file.py:123`
- **Confiance** : Haute - confirmé par traçage du code
- **La question** : L'utilisateur A peut-il accéder aux documents de l'utilisateur B ?
- **Investigation** :
  1. Tracé GET /api/documents/{pk}/ vers DocumentViewSet
  2. Vérifié get_queryset() - retourne Document.objects.all()
  3. Vérifié permission_classes - uniquement IsAuthenticated
  4. Vérifié has_object_permission() - non implémenté
  5. Vérifié qu'aucune vérification middleware ou classe de base pertinente n'existe
- **Preuves** : [Extrait de code montrant la lacune]
- **Impact** : Tout utilisateur authentifié peut lire n'importe quel document par ID
- **Correctif suggéré** : [Code qui applique l'autorisation - PAS un commentaire]

### Nécessite une vérification manuelle
[Problèmes où l'autorisation existe mais n'a pas pu être confirmée]

### Domaines non examinés
[Endpoints ou flux non couverts dans cet examen]

Motifs courants d'autorisation Django

Ce sont des motifs que vous pourriez trouver - pas une checklist à correspondre.

Scoping de requête

# Limité au scope de l'utilisateur
Document.objects.filter(owner=request.user)

# Limité au scope de l'organisation
Document.objects.filter(organization=request.user.organization)

# Utilisant un manager personnalisé
Document.objects.for_user(request.user)  # Investiguer ce que cela fait

Application de permission

# Classes de permission DRF
permission_classes = [IsAuthenticated, IsOwner]

# has_object_permission personnalisé
def has_object_permission(self, request, view, obj):
    return obj.owner == request.user

# Décorateurs Django
@permission_required('app.view_document')

# Vérifications manuelles
if document.owner != request.user:
    raise PermissionDenied()

Attribution de propriété

# Côté serveur (sûr)
def perform_create(self, serializer):
    serializer.save(owner=self.request.user)

# À partir de la requête (investiguer)
serializer.save(**request.data)  # request.data inclut-il le propriétaire ?

Checklist d'investigation

Utilisez ceci pour guider votre examen, pas comme une checklist réussi/échoué :

□ Je comprends comment l'autorisation est généralement implémentée dans cette base de code
□ J'ai identifié le modèle de propriété (utilisateur, org, tenant, etc.)
□ J'ai cartographié les endpoints clés qui gèrent les données utilisateur
□ Pour chaque endpoint sensible, j'ai tracé le flux et posé :
  - D'où vient l'ID ?
  - Où les données sont-elles récupérées ?
  - Quelles vérifications existent entre l'entrée et l'accès aux données ?
□ J'ai vérifié mes résultats en vérifiant les classes parentes et le middleware
□ J'ai signalé uniquement les problèmes que j'ai confirmés par investigation

Skills similaires