Files
gauvainboiche 0d071d2843 Semaine 11
2026-07-11 21:38:00 +02:00

97 lines
4.0 KiB
Markdown

# Exo 5.6 "Audit Capstone"
## Le code à auditer
```python
# audit/views.py (API VOLONTAIREMENT VULNÉRABLE — à auditer)
import requests
from rest_framework import viewsets, serializers
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.permissions import AllowAny
from .models import Equipement
class EquipementSerializer(serializers.ModelSerializer):
class Meta:
model = Equipement
fields = "__all__"
class EquipementViewSet(viewsets.ModelViewSet):
queryset = Equipement.objects.all()
serializer_class = EquipementSerializer
permission_classes = [AllowAny]
@action(detail=False, methods=["post"])
def tester_webhook(self, request):
url = request.data["url"]
reponse = requests.get(url)
return Response({"status": reponse.status_code, "corps": reponse.text})
```
## Constats
### 1. "fields" ouvert à tous les champs [API3]
L'usage de `fields = "__all__"` permet une récupération dynamique de tous les champs de l'élément, y compris les champs éventuellement sensibles.
#### Correction
Usage d'un `field = ["champ1", "champ2", "champ3"]` selon les informations souhaitées.
### 2. queryset trop permissif [API1]
`queryset = Equipement.objects.all()` permet la récupération de tous les objets d'une table, sans limite de propriété ou d'état connu.
#### Correction
Définir une fonction `def get_queryset(self):` permettant de trier selon une propriété ciblée des objets itérables par l'utilisateur, par exemple s'il est propriétaire avec `return Equipement.objects.filter(owner=self.request.user)`
### 3. permission_classes trop permissif [API5]
`permission_classes = [AllowAny]` permet une connexion et des actions depuis n'importe quel compte, y compris les non-authentifiés.
#### Correction
Utiliser des permissions restrictives comme `permission_classes = [isAuthenticated]` voire des classes "isOwner" récupérant dynamiquement les infos.
### 4. url = request.data trop ouvert [API7]
Le bloc :
```
url = request.data["url"]
reponse = requests.get(url)
```
Permet à n'importe quelle source d'imposer une requête, avec Falsification des Requêtes Côté-Serveur. Non je ne le dirais pas en anglais (parce que j'ai le droit).
Par exemple (pour l'avoir déjà vu auparavant), la requête avec une URL à `http://169.254.169.254/latest/meta-data/iam/security-credentials/` peut faire afficher des crédits AWS, avec ID et clef secrète. Et on peut taper avec des noms de services genre `http://postgres:5432/` et potentiellement tomber sur une page de connexion. Pire, si on ne limite pas le type de protocole à `http://` on peut aussi bien tenter un `file:///etc/passwd`.
De plus, une absence d'URL peut mener à des erreurs serveurs et un Déni de Service.
#### Correction
Utiliser des sources précises validées avec une fonction `_hostname_resolve` pour vérifier que le point d'entrée correspond au point de sortie, avec système de liste blanche explicite - sinon liste noire par défaut -, interdire les redirections, restreindre à HTTP et HTTPS, mettre des `timeout` et autre limite de requêtes, etc.
Contrôler la présence du champ "URL" ou renvoyer une erreur avec un bloc `try: except:`.
### 5. Requêtes sans garde-fou / pas de limite [API4]
`reponse = requests.get(url)` se fait sans délai précisé entre deux requêtes. L'attaque par déni de service est probable.
#### Correction
Ralentir avec une API externe (genre Fail2Ban) ou un `sleep()`. Possibilité de limiter avec une énumération des requêtes.
### 6. Réponse de la requête brute [API3]
`reponse.text` renvoie une réponse brute, non formatée. Cela peut faire fuiter une surface d'attaque par injection de script.
#### Correction
Retourner une ligne de caractères fixée côté serveur avant renvoi au client. Aucun envoi direct d'une variable. Du genre :
```python
contenu = reponse.raw.read(settings.WEBHOOK_MAX_RESPONSE_BYTES)
return Response({"status": reponse.status_code, "corps": contenu.decode("utf-8", errors="replace")})
```