# 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")})
```