<!-- 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 :
- Comprenez comment l'autorisation fonctionne dans CETTE base de code
- Posez des questions sur les flux de données spécifiques
- Tracez le code pour trouver où (ou si) les vérifications d'accès se produisent
- 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