Semaine 11
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
# 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")})
|
||||
```
|
||||
Reference in New Issue
Block a user