Mass assignment (CWE-915, BOPLA) : injection via corps HTTP, champ JSON, mutations GraphQL, attributs ORM imbriqués — tableau comparatif et workflow de détection.
TL;DR
Le mass assignment est une classe de vulnérabilité où un framework web ou ORM mappe automatiquement les paramètres de requête HTTP sur les attributs d'un objet côté serveur sans restreindre les champs que l'appelant peut écrire. L'OWASP API Security Top 10 2023 le classe sous API3:2023 BOPLA (Broken Object Property Level Authorization), fusionnant les termes "Mass Assignment" (écriture) et "Excessive Data Exposure" (lecture).
La vulnérabilité existe partout où des données contrôlées par l'utilisateur s'écoulent dans un appel de liaison de modèle sans liste blanche explicite.
La variante la plus simple et la plus prévalente. L'attaquant ajoute des champs privilégiés directement au corps JSON d'une requête PATCH ou PUT sur un endpoint de mise à jour de profil ou de compte.
Endpoints cibles : PATCH /users/me, PUT /profile, PATCH /accounts/:id, POST /register.
Champs payload clés : role, is_admin, isAdmin, is_superuser, subscription_tier, credit_balance, verified, mfa_required: false.
La désérialisation JSON effectue un mappage de champs profond. Un attaquant exploite cela en injectant des champs à des profondeurs de nidification inattendues, en utilisant des clés de type opérateur, ou en envoyant des noms de propriétés que le schéma OpenAPI marque comme readOnly.
| Framework | Pattern vulnérable | Pattern sécurisé |
|---|---|---|
| Rails | params.require(:user).permit! | permit(:email, :name) |
| Django DRF | Meta.fields = '__all__' | fields = ['email', 'name'] |
| Express/Mongoose | User.findByIdAndUpdate(id, req.body) | Déstructurer uniquement les champs autorisés |
| Spring Boot | Pas de classe DTO | UserUpdateDto dédié |
| Laravel | $fillable manquant | $fillable = ['email', 'name'] |
| FastAPI | extra="allow" | extra="forbid" |
GraphQL ne restreint pas automatiquement les champs InputObject qui atteignent le résolveur. Les plateformes low-code génèrent des mutations depuis le schéma de base de données, rendant chaque colonne accessible en écriture. CVE-2024-52034 et CVE-2025-32433 suivent tous deux ce pattern.
accepts_nested_attributes_for de Rails permet à un modèle parent d'écrire sur des modèles associés via la syntaxe de hachage imbriqué. Les sérialiseurs de champs liés de Django et les chemins populate de Mongoose ont des patterns analogues.
{
"user": {
"email": "attaquant@example.com",
"role_attributes": {"name": "admin", "id": 1},
"permissions_attributes": [{"name": "delete:all", "_destroy": false}]
}
}RFC 6902 JSON Patch — remplacement de champ par chemin :
[{"op": "replace", "path": "/role", "value": "admin"}]RFC 7396 JSON Merge Patch — les valeurs null suppriment les clés per spéc RFC :
{"member_acl": null, "private": false}Étape 1 : GET /objet-cible → relever tous les champs y compris les privilégiés
Étape 2 : PATCH /objet-cible avec superensemble (30+ champs candidats ajoutés)
Étape 3 : GET /objet-cible avec un token de SESSION DIFFÉRENT
Étape 4 : Diff réponse Étape 1 vs Étape 3
Étape 5 : Confirmer effet secondaire observable (accès admin, déblocage fonctionnalité)Patterns de faux positifs à éliminer : 200 silencieux Rails (champs filtrés, non écrits), réponses echo (serveur reflète l'input sans persister), champs calculés (updated_at change toujours).
| Framework | Contrôle principal | Mode strict |
|---|---|---|
| Rails | params.require().permit() | action_on_unpermitted_parameters = :raise |
| Django DRF | fields = [...] explicite dans Meta | read_only_fields pour attributs privilégiés |
| Express | Déstructurer depuis req.body | Mongoose strict: 'throw' |
| Spring | Classe UserUpdateDto | @InitBinder setAllowedFields() |
| FastAPI | extra="forbid" dans model_config | Comportement par défaut Pydantic v2 |
| NestJS | ValidationPipe({ whitelist: true }) | forbidNonWhitelisted: true |
Les variantes incluent : injection dans le corps HTTP (champs supplémentaires dans POST/PATCH), injection de champ JSON (propriétés d'objets imbriqués), patterns ORM spécifiques (bypass strong_params Rails, Django __all__, spread Express), abus d'input de mutation GraphQL, injection d'attributs imbriqués (Rails accepts_nested_attributes_for), remplacement de champ JSON Patch RFC 6902, effacement null JSON Merge Patch RFC 7396, smuggling HTTP Method Override, pollution de prototype via __proto__, et chaînes d'injection d'opérateur NoSQL.
L'injection directe de champ JSON dans les endpoints REST PATCH est le pattern le plus répandu en 2024-2026, affectant environ 30 % des APIs analysées selon la recherche OWASP API Security. Le mass assignment par mutation GraphQL est en forte croissance, notamment dans les plateformes headless CMS (Strapi CVE-2024-52034, Directus CVE-2025-32433).
Tout framework avec liaison automatique est affecté. Par ordre de prévalence : Rails (bypass strong_params ou permit manquant), Django DRF (Meta.fields='__all__'), Express/Mongoose (spread req.body), Spring Boot (DTO manquant), Laravel (modèles Eloquent non gardés), FastAPI/Pydantic (extra='allow'), et tout headless CMS avec endpoints auto-CRUD (Strapi, Directus, NocoDB).
Directement, rarement. Indirectement, oui. Chaîne : le mass assignment envoie __proto__: {isAdmin: true} à un endpoint Node.js utilisant lodash.merge, corrompant Object.prototype. Une bibliothèque aval utilisant Object.shell déclenche child_process.exec (RCE). L'impact critique le plus courant est la prise de contrôle de compte et l'escalade de privilèges.
Le CVSS varie selon le contexte. Un mass assignment non authentifié (POST /register avec role=admin) peut atteindre CVSS 9.1+. Authentifié mais faible privilège (PATCH /me avec is_admin=true) est typiquement CVSS 8.1 (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N). Les chaînes de pollution de prototype atteignent 8.8-9.8.
Les plateformes low-code/no-code (Strapi, Directus, NocoDB, Hasura, PostgREST) génèrent des endpoints CRUD automatiquement à partir des modèles de données, sans contrôle d'accès au niveau des champs par défaut. Toutes les colonnes de la table deviennent accessibles en écriture via l'API générée sauf si explicitement bloquées. CVE-2024-52034, CVE-2025-32433 et CVE-2024-32887 exploitent tous ce pattern.
Une liste blanche (permit list) définit explicitement les champs qu'un utilisateur peut écrire — tout champ absent est rejeté. Une liste noire essaie d'énumérer les champs interdits et échoue quand des développeurs ajoutent de nouveaux champs sensibles sans mettre à jour la liste. Les listes blanches sont le seul pattern fiable.
Arjun v3.x pour les APIs REST (découverte de paramètres avec support corps JSON). x8 pour GraphQL et corps imbriqués profonds. Burp Suite Param Miner pour la découverte de paramètres cachés par différentiel de réponse. OpenAPI-fuzzer pour les APIs avec schémas documentés (cible les champs readOnly pour tentative d'écriture). BreachVex combine ces quatre approches dans ses tests de sécurité d'API automatisés.
Pas sans confirmation. Un 200 OK est nécessaire mais insuffisant. Vous devez confirmer la persistance : GET la ressource avec un token de session différent et vérifier que le champ injecté persiste avec la valeur contrôlée par l'attaquant. Sans confirmation inter-session, la découverte a un taux de faux positifs élevé.
La BFLA (Broken Function Level Authorization) contrôle si vous pouvez appeler un endpoint. La BOPLA contrôle si vous pouvez écrire des champs spécifiques sur des objets auxquels vous accédez légitimement. Elles sont fréquemment chaînées : la BFLA atteint un endpoint de mise à jour réservé aux admins, la BOPLA définit des champs privilégiés sur l'objet cible. Argo CD CVE-2024-21652 illustre une chaîne BFLA+BOPLA.