protection de l'application contre les attaques numériques

This commit is contained in:
innotex
2026-01-16 20:10:17 +01:00
parent 520166a1e9
commit de157e9d0e
11 changed files with 1350 additions and 44 deletions

View File

@@ -0,0 +1,200 @@
"""
Validateurs de sécurité pour prévenir les injections et attaques
"""
import re
from pathlib import Path
from typing import Optional
from fastapi import HTTPException, status
import logging
logger = logging.getLogger("innotexboard.security")
def validate_container_name(name: str) -> str:
"""
Valide un nom de conteneur Docker
Protection contre injection de commandes
"""
if not name or len(name) > 255:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Nom de conteneur invalide"
)
# Docker accepte: lettres, chiffres, tirets, underscores, points
pattern = re.compile(r'^[a-zA-Z0-9][a-zA-Z0-9_.-]*$')
if not pattern.match(name):
logger.warning(f"Invalid container name attempted: {name}")
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Nom de conteneur contient des caractères invalides"
)
return name
def validate_image_name(image: str) -> str:
"""
Valide un nom d'image Docker
Format: [registry/]repository[:tag]
"""
if not image or len(image) > 512:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Nom d'image invalide"
)
# Pattern pour image Docker valide
pattern = re.compile(
r'^(?:[a-z0-9]+(?:[._-][a-z0-9]+)*/)*'
r'[a-z0-9]+(?:[._-][a-z0-9]+)*'
r'(?::[a-zA-Z0-9][a-zA-Z0-9._-]*)?$'
)
if not pattern.match(image):
logger.warning(f"Invalid image name attempted: {image}")
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Nom d'image contient des caractères invalides"
)
return image
def validate_file_path(file_path: str, allowed_base: str = "/home/innotex/Docker") -> Path:
"""
Valide un chemin de fichier et prévient le path traversal
Vérifie que le chemin reste dans le répertoire autorisé
"""
try:
# Convertir en Path absolu et résoudre les liens symboliques
target_path = Path(file_path).resolve()
base_path = Path(allowed_base).resolve()
# Vérifier que le chemin est dans le répertoire autorisé
if not str(target_path).startswith(str(base_path)):
logger.warning(f"Path traversal attempt detected: {file_path}")
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Accès au chemin non autorisé"
)
return target_path
except (ValueError, RuntimeError) as e:
logger.warning(f"Invalid path attempted: {file_path} - {e}")
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Chemin de fichier invalide"
)
def validate_compose_name(name: str) -> str:
"""
Valide un nom de fichier docker-compose
Accepte uniquement les noms de fichiers sûrs
"""
if not name or len(name) > 100:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Nom de compose invalide"
)
# Accepter uniquement alphanumériques, tirets, underscores
pattern = re.compile(r'^[a-zA-Z0-9_-]+$')
if not pattern.match(name):
logger.warning(f"Invalid compose name attempted: {name}")
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Nom de compose contient des caractères invalides"
)
return name
def validate_package_name(package: str) -> str:
"""
Valide un nom de package Debian/APT
Protection contre injection de commandes
"""
if not package or len(package) > 200:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Nom de package invalide"
)
# Pattern pour nom de package Debian valide
pattern = re.compile(r'^[a-z0-9][a-z0-9+.-]*$')
if not pattern.match(package):
logger.warning(f"Invalid package name attempted: {package}")
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Nom de package contient des caractères invalides"
)
return package
def sanitize_command_output(output: str, max_length: int = 10000) -> str:
"""
Nettoie la sortie de commande pour éviter l'injection de contenu malveillant
Limite la taille pour prévenir DoS
"""
if not output:
return ""
# Limiter la taille
if len(output) > max_length:
output = output[:max_length] + "\n[...truncated...]"
# Retirer les caractères de contrôle dangereux (sauf newline et tab)
sanitized = re.sub(r'[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F]', '', output)
return sanitized
def validate_port(port: int) -> int:
"""
Valide un numéro de port
"""
if not isinstance(port, int) or port < 1 or port > 65535:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Port invalide: {port}. Doit être entre 1 et 65535"
)
# Ports privilégiés (< 1024) nécessitent root
if port < 1024:
logger.warning(f"Privileged port requested: {port}")
return port
def validate_environment_variable(key: str, value: str) -> tuple[str, str]:
"""
Valide une variable d'environnement
Protection contre injection
"""
# Clé: lettres, chiffres, underscores uniquement
key_pattern = re.compile(r'^[A-Z_][A-Z0-9_]*$')
if not key_pattern.match(key):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Nom de variable d'environnement invalide: {key}"
)
# Valeur: pas de null bytes
if '\x00' in value:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Valeur de variable d'environnement contient des caractères invalides"
)
# Limiter la taille
if len(value) > 10000:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Valeur de variable d'environnement trop longue"
)
return key, value