# Démonstration: OWASP A01:2025 - Broken Access Control
Cette démonstration illustre la faille OWASP A01:2025 (Broken Access Control) en utilisant un projet Django moderne.
Nous allons simuler le cas le plus courant : **l'IDOR (Insecure Direct Object Reference)**.
## 1. Objectif
Démontrer comment un utilisateur peut voir les données d'un autre utilisateur en modifiant simplement un ID dans l'URL, et comment y remédier.
## 2. Configuration du projet
1. **Installer les dépendances :**
```bash
poetry install
```
2. **Créer les migrations :**
```bash
python manage.py makemigrations profiles
```
3. **Appliquer les migrations (créer la base de données) :**
```bash
python manage.py migrate
```
4. **Créer des utilisateurs de test :**
Nous avons besoin d'au moins deux utilisateurs pour tester.
* **Créez un superutilisateur (admin) :**
```bash
python manage.py createsuperuser
```
(Par exemple : `admin` / `password123`)
* **Créez un utilisateur normal (user1) :**
Lancez à nouveau :
```bash
python manage.py createsuperuser
```
(Par exemple : `user1` / `password123`)
*Note : Nous utilisons `createsuperuser` pour `user1` par simplicité, mais ne lui donnez pas le statut de "superuser".*
5. **Lancer le serveur de test :**
```bash
python manage.py runserver
```
## 3. Scénario de test (la faille)
1. **Connectez-vous en tant qu'admin** sur [http://127.0.0.1:8000/accounts/login/](http://127.0.0.1:8000/accounts/login/).
2. Allez sur l'interface d'admin : [http://127.0.0.1:8000/admin/](http://127.0.0.1:8000/admin/)
3. Allez dans "Profiles". Vous verrez deux profils, un pour `admin` (ID 1) et un pour `user1` (ID 2).
4. Cliquez sur le profil de `admin` (ID 1) et ajoutez une "Note secrète", par exemple : `Secret de l'admin`.
5. Cliquez sur le profil de `user1` (ID 2) et ajoutez une "Note secrète", par exemple : `Top secret de user1`.
6. **Déconnectez-vous** (http://127.0.0.1:8000/accounts/logout/).
---
### **L'attaque (IDOR)**
1. **Connectez-vous en tant que `user1`** sur [http://127.0.0.1:8000/accounts/login/](http://127.0.0.1:8000/accounts/login/).
2. Allez sur la page d'accueil (http://127.0.0.1:8000/) et cliquez sur le lien "Voir mon profil (VULNÉRABLE)".
3. Vous arrivez sur `http://127.0.0.1:8000/profile/vulnerable/2/`. Vous voyez bien votre secret : `Top secret de user1`.
4. **Maintenant, modifiez l'URL dans votre navigateur.** Changez l'ID `2` par l'ID `1` (le profil de l'admin) :
`http://127.0.0.1:8000/profile/vulnerable/1/`
5. **PROBLÈME :** Vous êtes connecté en tant que `user1`, mais vous pouvez voir le secret de `admin` (`Secret de l'admin`) !
C'est une faille "Broken Access Control" de type IDOR. La vue `VulnerableProfileView` vérifie bien *si* l'utilisateur est connecté, mais elle ne vérifie pas *si* l'utilisateur connecté a le droit de voir *cet objet spécifique*.
```mermaid
graph TD
subgraph "Scénario de l'attaque IDOR"
A["User1 (ID=2) se connecte"] --> B{"User1 demande /profile/vulnerable/2/"};
B --> C["Serveur vérifie: user connecté? Oui"];
C --> D["Serveur renvoie 200 OK
'Secret de user1'"];
D --> E{"User1 modifie l'URL
demande /profile/vulnerable/1/"};
E --> F["Serveur vérifie: user connecté? Oui"];
F --> G["Serveur renvoie 200 OK
'Secret de admin' (ID=1)"];
G --> H[("FAILLE DE SÉCURITÉ")];
end
```
## 4. La correction
1. En étant toujours connecté en tant que `user1`, retournez à l'accueil (http://127.0.0.1:8000/).
2. Cliquez sur le lien "Voir mon profil (SÉCURISÉ)".
3. Vous arrivez sur `http://127.0.0.1:8000/profile/secure/2/`. Tout fonctionne, vous voyez votre secret.
4. **Maintenant, tentez la même attaque.** Modifiez l'URL et remplacez `2` par `1` :
`http://127.0.0.1:8000/profile/secure/1/`
5. **SOLUTION :** Django vous renvoie une erreur **"403 Forbidden"**. L'accès est refusé.
```mermaid
graph TD
subgraph "Flux de la vue sécurisée"
A{"User1 (ID=2) demande /profile/secure/1/"} --> B["Serveur vérifie: user connecté? Oui"];
B --> C{"Serveur exécute test_func:
request.user == profile.user?"};
C -- "Renvoie False" --> D["Serveur renvoie 403 Forbidden"];
D --> E[("ACCÈS REFUSÉ")];
end
```
## 5. Explication du code corrigé
Ouvrez le fichier `profiles/views.py`.
* **`VulnerableProfileView`** :
```python
class VulnerableProfileView(LoginRequiredMixin, DetailView):
model = Profile
template_name = 'profiles/profile_detail.html'
# VULNÉRABILITÉ: Ne vérifie pas QUI est l'utilisateur!
# Elle cherche juste l'objet Profile par son ID (pk).
```
Cette vue hérite de `LoginRequiredMixin` (l'utilisateur doit être connecté) et `DetailView` (charge un objet par son `pk`). Elle ne vérifie jamais si `request.user` est le propriétaire du `Profile` qu'elle affiche.
* **`SecureProfileView`** :
```python
class SecureProfileView(LoginRequiredMixin, UserPassesTestMixin, DetailView):
model = Profile
template_name = 'profiles/profile_detail.html'
# CORRECTION: On utilise UserPassesTestMixin pour vérifier la permission
def test_func(self):
# Récupère l'objet profile que DetailView a trouvé
profile = self.get_object()
# Vérifie si l'utilisateur connecté est le propriétaire du profil
return self.request.user == profile.user
# Si test_func renvoie False, Django renvoie un 403 Forbidden.
```
Cette vue ajoute le `UserPassesTestMixin`. Ce mixin exécute la méthode `test_func()` avant d'afficher la page. Si `test_func()` renvoie `True`, la page s'affiche. Si elle renvoie `False`, l'utilisateur reçoit une erreur 403.
Ici, on vérifie simplement que l'utilisateur de la requête (`self.request.user`) est bien le même que l'utilisateur lié au profil (`profile.user`). C'est la façon "moderne" et "Django" de gérer le contrôle d'accès au niveau de l'objet.
## 6. Allez plus loin avec l'outil OWASP ZAP
Les failles comme l'IDOR (testée manuellement) ou les pages "oubliées" sont courantes. Des outils comme **OWASP ZAP** (Zed Attack Proxy) sont conçus pour les trouver. ZAP fonctionne comme un "proxy" : il s'intercale entre votre navigateur et le site web pour intercepter, analyser et attaquer le trafic.
Voici deux exemples d'utilisation de ZAP sur notre projet.
### Partie 1 : Scan automatisé et autres failles (alertes)
ZAP dispose d'un **"Automated Scan"** (Scan Automatisé) qui va "spider" (parcourir) tout le site puis lancer une série de tests d'attaque sur chaque page trouvée pour y déceler des failles de configuration, des vulnérabilités XSS, et bien plus.
**Comment lancer ce scan :**
1. Ouvrez OWASP ZAP.
2. Dans la fenêtre principale, assurez-vous d'être sur l'onglet **"Démarrage"**.
3. Entrez l'URL de votre application Django locale : `http://127.0.0.1:8000`
4. Cliquez sur le bouton **"Attaquer"** pour lancer le scan automatisé.
5. ZAP va s'occuper de tout. Attendez simplement que le scan se termine.
6. Une fois le scan terminé, cliquez sur l'onglet **"Alertes"** en bas pour voir les résultats.
```mermaid
graph TD
subgraph "Processus du scan automatisé ZAP"
A["Entrer l'URL cible"] --> B["Cliquer 'Attaquer'"];
B --> C["ZAP explore le site 'Spider'"];
C --> D["ZAP lance des attaques 'Active Scan'"];
D --> E["Résultats affichés dans l'onglet 'Alertes'"];
E --> F["Exemple: 'Server Leaks Version'"];
end
```
**Résultat sur notre projet :**
Le screenshot ci-dessous montre l'onglet "Alertes" après le scan. ZAP a trouvé de nombreux problèmes qui ne sont pas liés à notre code, mais à la *configuration* du serveur (A02:2025 - Security Misconfiguration).

* **Analyse d'une alerte : "Server Leaks Version Information..."**
* **Ce que ZAP a trouvé :** L'alerte en surbrillance nous dit que le serveur est trop "bavard". Dans l'en-tête (Header) de sa réponse, il inclut : `WSGIServer/0.2 CPython/3.13.5`.
* **Pourquoi est-ce un problème ?** Un attaquant voit maintenant que nous utilisons `CPython 3.13.5`. S'il existe une faille de sécurité *connue* (une CVE) pour cette version, l'attaquant peut l'exploiter directement.
* **Autres alertes visibles :** ZAP a également trouvé "Cookie No HttpOnly Flag" et "Content Security Policy (CSP) Header Not Set".
---
### Partie 2 : Découverte de pages cachées (Parcours Forcés)
La faille A01 (Broken Access Control) ne concerne pas seulement les objets (voir le profil d'un autre), mais aussi l'accès à des fonctionnalités entières. L'outil **"Parcours forcés"** de ZAP est conçu pour trouver les pages "cachées" en testant une liste de noms courants.
**Comment lancer ce scan :**
1. Dans ZAP, assurez-vous d'avoir visité votre site (`http://127.0.0.1:8000`) pour qu'il apparaisse dans "l'arborescence des Sites" à gauche.
2. Allez dans le menu `Vue`, puis `Afficher l'onglet`, puis `Onglet parcours Forcés`.
3. ZAP vous proposera une liste de dictionnaires. Choisissez-en un (ici: `directory-list-1.0.txt`) et lancez le scan.
4. Cliquez sur l'onglet **"Parcours forcés"** pour voir les résultats arriver en direct.
5. Au bout de 30000 requêtes environ (position du mot `protected` dans le fichier `directory-list-1.0.txt`), vous devriez voir la page `/protected/` apparaître avec un code **200 OK**.
6. Vous pouvez alors arrêter le scan.
```mermaid
graph TD
subgraph "Processus du parcours forcé ZAP"
A["Lancer 'Parcours forcés'
avec un dictionnaire"] --> B{"ZAP teste des URL"};
B -- "/fake_page/" --> C["Serveur répond 404"];
B -- "/admin/" --> D["Serveur répond 302"];
B -- "..." --> B;
B -- "/protected/" --> E["Serveur répond 200 OK"];
E --> F[("PAGE CACHÉE TROUVÉE")];
end
```
**Résultat sur notre projet :**
En lançant l'outil, ZAP a testé des centaines de noms. Comme le mot `protected` est très courant dans ces listes, il a testé `http://127.0.0.1:8000/protected/` et a reçu un code **200 OK**. ZAP a donc prouvé l'existence d'une page que nous n'étions pas censés connaître.

---
### Astuce : regardez votre console Django pendant le scan !
Pendant que vous lancez des outils comme "Parcours forcés" ou "Scan Actif" dans ZAP, gardez un œil sur le terminal où tourne votre serveur Django (`python manage.py runserver`).
Vous n'y verrez pas un flot lent de requêtes légitimes. À la place, vous verrez votre console défiler à toute vitesse, submergée par des centaines ou des milliers de requêtes en quelques secondes.
**Pourquoi c'est instructif ?**
* **Vous voyez l'attaque :** C'est la signature visuelle d'un scan automatisé. Vous verrez ZAP tester des URL qui n'existent pas (ce qui générera des `404 Not Found`) jusqu'à ce qu'il tombe sur une URL valide.
* **Comprendre les codes de statut :** Vous verrez le serveur répondre :
* `GET /admin/ 302 Found` (Redirection vers le login)
* `GET /fake_page/ 404 Not Found`
* `GET /backup/ 404 Not Found`
* `GET /protected/ 200 OK` (La requête gagnante !)
* **Détection :** C'est exactement ce que les administrateurs système et les outils de sécurité (comme un WAF ou un SIEM) recherchent. Un nombre massif de requêtes 404 provenant d'une seule adresse IP est un signe évident qu'un scan de découverte (reconnaissance) est en cours.