Votre service dispose d’une clé API pour votre stockage cloud. Un utilisateur envoie une requête. Votre service écrit fidèlement un fichier. Le fichier atterrit dans le bucket de l’attaquant, pas dans le vôtre, et maintenant ils ont vos données. Vous venez de devenir un complice involontaire.

Il s’agit d’une attaque par député confus. L’attaquant ne peut pas écrire dans le bucket cible lui-même. Il lui manque la permission. Mais il peut tromper votre service, qui dispose de la permission, pour qu’il le fasse à sa place. Votre service est le « député ». Il est « confus » quant à pour qui l’action est réellement effectuée.

À quoi ressemble réellement une attaque par député confus

L’exemple classique est un compilateur. Le compilateur s’exécute avec les privilèges de votre utilisateur, afin qu’il puisse lire ses fichiers source et écrire des fichiers objets dans son répertoire personnel. Un utilisateur malveillant fournit au compilateur un fichier source qui contient des directives pour écrire la sortie dans /etc/passwd au lieu du répertoire de build. Le compilateur détient l’autorité. L’utilisateur ne la détient pas. Le compilateur est confus quant à l’intention qu’il exécute.

Les services web modernes recréent ce schéma constamment. Considérez un service d’upload de fichiers qui permet aux utilisateurs de fournir une URL de callback :

# The user provides a URL. We fetch it and store it.
def upload_from_url(user_id: str, source_url: str, dest_path: str):
    response = requests.get(source_url, timeout=30)
    s3.put_object(Bucket="my-app-bucket", Key=dest_path, Body=response.content)
    audit_log.info(f"User {user_id} uploaded to {dest_path}")

Cela semble innocent. L’utilisateur fournit une URL. Vous la récupérez. Vous la stockez dans votre bucket. Et si source_url était http://169.254.169.254/latest/meta-data/iam/security-credentials/ ? Vous êtes en train d’exfiltrer vos propres credentials AWS et de les écrire dans un chemin nommé par l’attaquant. Votre rôle IAM détient l’autorité. L’attaquant ne la détient pas. Vous êtes le député confus.

L’attaque se généralise à tout système où un composant privilégié effectue une action au nom d’un demandeur moins privilégié, et le demandeur peut influencer les paramètres de cette action.

Pourquoi les vérifications d’identité ne suffisent pas

Le correctif instinctif est de vérifier qui est le demandeur. « Cet utilisateur est-il authentifié ? » Oui. « Cet utilisateur est-il autorisé à uploader des fichiers ? » Oui. Les deux vérifications passent, et l’attaque fonctionne toujours.

Le problème est que les vérifications d’autorisation vérifient si le demandeur peut effectuer une action. Elles ne vérifient pas si la cible de l’action est appropriée pour ce demandeur. L’utilisateur est autorisé à uploader. Il n’est pas autorisé à faire en sorte que votre service récupère ses propres métadonnées et les stocke sous son contrôle. Le modèle de permissions confond « peut utiliser le endpoint d’upload » avec « peut faire effectuer au service des actions privilégiées arbitraires ».

C’est le mode de défaillance principal : le député détient une capability (credentials AWS, accès au système de fichiers, accès en écriture à la base de données) et l’utilise sur la base d’instructions provenant de quelqu’un d’autre. Sans lier la capability à l’intention spécifique du détenteur d’autorité original, la confusion est inévitable.

Sécurité basée sur les capabilities : lier l’autorité à l’intention

La sécurité basée sur les capabilities corrige cela en rendant l’autorité infalsifiable et explicite. Au lieu de demander « qui êtes-vous ? » puis de chercher ce que vous êtes autorisé à faire, une capability est un jeton qui confère directement le droit d’effectuer une action spécifique sur une ressource spécifique.

Dans un système de capabilities, le service d’upload ne détiendrait pas une clé d’écriture S3 générique. Il détiendrait une capability pour écrire dans des chemins spécifiques, limités à des utilisateurs spécifiques. La requête de l’utilisateur inclurait un jeton de capability qui nommerait la destination exacte. Le service vérifierait la signature cryptographique du jeton et sa portée, pas l’identité de l’utilisateur contre une table de permissions globale.

Voici un exemple simplifié utilisant des jetons limités :

import hashlib
import hmac
import secrets

SECRET = secrets.token_bytes(32)

def mint_capability(principal: str, action: str, resource: str) -> str:
    """Create an unforgeable capability token scoped to a specific action and resource."""
    payload = f"{principal}:{action}:{resource}"
    sig = hmac.new(SECRET, payload.encode(), hashlib.sha256).hexdigest()[:16]
    return f"{payload}:{sig}"

def verify_capability(token: str, expected_action: str, expected_resource: str) -> bool:
    """Verify a capability token matches the expected action and resource."""
    try:
        principal, action, resource, sig = token.rsplit(":", 3)
    except ValueError:
        return False
    payload = f"{principal}:{action}:{resource}"
    expected_sig = hmac.new(SECRET, payload.encode(), hashlib.sha256).hexdigest()[:16]
    return hmac.compare_digest(sig, expected_sig) and action == expected_action and resource == expected_resource

# The user receives a capability that only allows writing to their own prefix.
user_id = "alice"
scoped_token = mint_capability(user_id, "write", f"uploads/{user_id}/report.pdf")

# Later, the service verifies the capability before acting.
if not verify_capability(scoped_token, "write", "uploads/alice/report.pdf"):
    raise PermissionError("Capability does not match requested action or resource")

s3.put_object(Bucket="my-app-bucket", Key="uploads/alice/report.pdf", Body=data)

L’idée clé : le jeton de capability scoped_token ne peut pas être rejoué pour écrire dans uploads/bob/salary.xlsx. Il est lié à une ressource spécifique au moment de la création. Le service ne décide pas de ce qui est autorisé en fonction de qui demande. Il décide en fonction de ce que le jeton autorise explicitement.

Quand les systèmes de capabilities sont impraticables

La plupart d’entre nous ne construisent pas de systèmes d’exploitation avec des architectures de capabilities pures. Nous appelons des APIs AWS, communiquons avec PostgreSQL, et écrivons des handlers HTTP. La sécurité basée sur les capabilities complète est une philosophie de conception, pas une bibliothèque prête à l’emploi.

Le compromis pratique est d’appliquer la pensée capability à vos frontières de service. Au lieu de donner à votre service un seul rôle IAM avec des permissions S3 larges, utilisez des URLs présignées limitées. Au lieu de laisser les utilisateurs nommer des destinations arbitraires, validez et sanitisez la ressource cible contre ce que cet utilisateur est autorisé à toucher. L’objectif n’est pas un OS capability formel. L’objectif est d’éliminer l’autorité implicite.

Voici le même endpoint d’upload avec une validation limitée au lieu d’une confiance générique :

import re

# A regex that constrains what a user can name as a destination.
ALLOWED_DEST_PATTERN = re.compile(r"^uploads/(?P<user_id>[a-z0-9_-]+)/[a-zA-Z0-9_.-]+$")

def upload_from_url(user_id: str, source_url: str, dest_path: str):
    match = ALLOWED_DEST_PATTERN.match(dest_path)
    if not match or match.group("user_id") != user_id:
        raise ValueError("Invalid destination path")

    # The source URL is also restricted. No internal IPs, no metadata endpoints.
    parsed = urlparse(source_url)
    if parsed.hostname in ("169.254.169.254", "localhost", "127.0.0.1"):
        raise ValueError("Forbidden source URL")

    response = requests.get(source_url, timeout=30)
    s3.put_object(Bucket="my-app-bucket", Key=dest_path, Body=response.content)

Ce n’est pas un système de capabilities au sens formel. Mais il applique le même principe : l’autorité du service n’est pas un chèque en blanc. Elle est limitée à un ensemble spécifique d’actions que le demandeur est autorisé à invoquer.

Pourquoi ce motif réapparaît constamment

Les vulnérabilités par député confus apparaissent dans les flux OAuth, les fonctions serverless, les handlers de webhooks, et les pipelines de supply chain. Chaque fois qu’un composant avec des privilèges élevés traite une entrée utilisateur et agit en conséquence, le risque est présent.

Le motif d’exfiltration de métadonnées via SSRF est si courant que les fournisseurs cloud bloquent maintenant l’IP IMDS par défaut dans les VPCs. C’est une atténuation pour un symptôme, pas pour la maladie. La maladie est un composant privilégié qui ne peut pas distinguer sa propre intention de l’intention du demandeur.

Prévenir les attaques par député confus en pratique

Vous n’avez pas besoin de réécrire votre architecture. Trois habitudes couvrent la plupart des cas.

Limitez vos credentials. Si votre service parle à S3, donnez-lui une politique IAM qui ne peut écrire que dans un préfixe spécifique. S’il parle à une base de données, utilisez la sécurité au niveau des lignes ou des credentials séparés par tenant. Plus le rayon d’explosion d’un député confus est petit, moins il peut faire de dégâts.

Validez la cible, pas seulement l’acteur. Les vérifications d’autorisation doivent répondre à deux questions : le demandeur est-il autorisé à effectuer cette action, et la cible de l’action est-elle dans la portée du demandeur ? Un utilisateur peut uploader des fichiers. Peut-il uploader des fichiers dans le répertoire d’un autre utilisateur ? Peut-il faire en sorte que votre service appelle des URLs arbitraires ? La deuxième question est là où vivent les députés confus.

Évitez l’autorité ambiante. L’autorité ambiante est lorsqu’un processus a des permissions simplement parce qu’il est, pas parce qu’il fait. Exécuter en tant que root. Utiliser une clé API maîtresse. Détenir une connexion superutilisateur à la base de données. Ce sont des choses pratiques et dangereuses. Divisez vos privilèges. Utilisez des credentials temporaires avec une expiration explicite. Plus votre autorité est granulaire, plus il est difficile de la confondre.

FAQ

Le CSRF est-il une attaque par député confus ?

Oui, en un sens. Le navigateur de l’utilisateur est le député. Il détient un cookie de session (la capability) et effectue une action modifiant l’état parce qu’un site malveillant lui a dit de le faire. Le navigateur est confus quant à savoir si la requête représente l’intention de l’utilisateur. Les tokens CSRF fonctionnent en liant la capability (le cookie) à un contexte d’action spécifique, ce qui est exactement le correctif basé sur les capabilities.

Cela s’applique-t-il aux microservices ?

Absolument. Les appels inter-services utilisent souvent un compte de service partagé avec des permissions larges. Si le Service A appelle le Service B avec un paramètre fourni par l’utilisateur, et que le Service B utilise son compte privilégié pour agir sur ce paramètre, le Service B est le député confus. Utilisez des tokens de service limités ou une autorisation spécifique à la requête.

Et l’injection SQL ?

L’injection SQL est une attaque par député confus sur votre base de données. La base de données est le député. Elle détient l’autorité de lire et d’écrire dans les tables. L’attaquant lui fournit des instructions déguisées en entrée. Les requêtes paramétrées corrigent cela en séparant l’autorité de la base de données (la structure de la requête) de l’entrée de l’utilisateur (les paramètres).

Auditez vos députés

Parcourez vos services et identifiez ceux qui détiennent des privilèges que les utilisateurs n’ont pas. Pour chacun, demandez-vous : un utilisateur pourrait-il tromper ce service pour qu’il utilise son autorité d’une manière qui profite à l’attaquant ? Si la réponse est oui, vous avez un député confus. Le correctif est rarement exotique. C’est généralement une question de limiter ce que le service est prêt à faire, et de refuser les requêtes qui sortent de cette portée.

Votre service ne devrait pas être un complice. Assurez-vous qu’il sait de qui il suit les ordres.