Mass assignment imbriqué (CWE-915) : objets JSON ou associations de relations contournant le filtrage top-level pour injecter des champs privilégiés dans les nœuds imbriqués.
TL;DR
role_attributes et permissions_attributes atteignent les modèles associésaccepts_nested_attributes_for de Rails + permit imbriqué manquant = création/suppression d'association par l'attaquantparams.expect() Rails 8 — la forme mono-bracket permet les tableaux de hachages pour les attributs imbriqués_destroy: true supprime les enregistrements associés — toutes les permissions effacées via l'endpoint de mise à jour parentLe mass assignment par objets imbriqués exploite les associations ORM où une mise à jour du modèle parent écrit sur des modèles enfants ou liés via la syntaxe de hachage imbriqué. Là où le mass assignment de niveau supérieur injecte role: "admin" comme champ direct, l'assignment imbriqué construit role_attributes: {name: "admin"} ou permissions_attributes: [{name: "delete:all"}] — des champs à un ou plusieurs niveaux de profondeur qui contournent toute liste blanche appliquée uniquement au niveau parent.
accepts_nested_attributes_for de Rails est la surface canonique, mais tout ORM avec support des relations a un pattern analogue : les sérialiseurs imbriqués de Django, populate de Mongoose, les associations GORM, et les writers de relations imbriqués de Laravel.
PATCH /api/v1/users/me HTTP/1.1
Content-Type: application/json
Authorization: Bearer <token_utilisateur>
{
"user": {
"email": "attaquant@example.com",
"role_attributes": {
"name": "admin",
"id": 1
},
"permissions_attributes": [
{"name": "delete:all", "_destroy": false},
{"name": "admin", "_destroy": false}
],
"organization_attributes": {
"id": 1,
"owner_id": 1
}
}
}PATCH /api/v1/users/me HTTP/1.1
Content-Type: application/json
{
"user": {
"permissions_attributes": [
{"id": 1, "_destroy": true},
{"id": 2, "_destroy": true},
{"id": 3, "_destroy": true}
]
}
}Supprime tous les enregistrements de permissions pour l'utilisateur cible via l'endpoint de mise à jour parent.
# VULNÉRABLE dans Rails 8 — mono-bracket permet tableau de hachages
def invoice_params
params.expect(invoice: [:title, line_items_attributes: [:description, :amount]])
end
# SÉCURISÉ dans Rails 8 — double-bracket restreint à un seul hachage
def invoice_params
params.expect(invoice: [:title, line_items_attributes: [[:description, :amount]]])
endPATCH /api/v1/users/me HTTP/1.1
Content-Type: application/json
{
"profile.role": "admin",
"account.subscription": {"tier": "enterprise", "status": "active"},
"organization.owner_id": 1
}Avec Mongoose et le support de chemin pointé, User.findByIdAndUpdate(id, req.body) traversera les chemins de sous-documents imbriqués.
// VULNÉRABLE — Save() met à jour toutes les associations y compris imbriquées
func UpdateUser(c *gin.Context) {
var user models.User
json.NewDecoder(c.Request.Body).Decode(&user) // Corps entier → struct
db.Save(&user) // GORM met à jour l'association Role si user.Role est défini
}{
"email": "attaquant@example.com",
"Role": {"name": "admin", "id": 1}
}GitHub 2012 — Homakov (Historique, sans CVE)
Egor Homakov a exploité les attributs imbriqués Rails sur GitHub.com en production. En postant une clé SSH avec owner_id=1 (référençant l'organisation rails/rails), il a associé sa clé à l'organisation. L'exploit a utilisé la mise à jour du modèle parent pour traverser une association et modifier le champ owner_id sur le modèle associé — du mass assignment imbriqué dans sa forme originale.
CVE-2024-39689 — Traversal chemin populate Mongoose (CVSS 7.5, 2024)
La résolution de chemin populate de Mongoose était exploitable quand des chemins de champs contrôlés par l'utilisateur étaient passés à findByIdAndUpdate. Injecter des directives populate Mongoose comme clés JSON imbriquées traversait vers des champs sur des modèles liés hors de la portée de mise à jour prévue.
CVE-2024-43788 — Chaîne pollution prototype webpack (CVSS 7.3, 2024)
L'utilitaire de fusion de webpack 5 propagait les clés __proto__ de JSON contrôlé par l'utilisateur dans les opérations de fusion imbriquées. Envoyer {"__proto__": {"isAdmin": true}} en position imbriquée empoisonnait le prototype global de Object, affectant toutes les vérifications de propriété suivantes dans le processus Node.js.
HackerOne #1069904 — GitLab Escalade d'admin imbriquée (6 337 $)
L'API utilisateur GitLab acceptait admin: true comme attribut imbriqué dans une requête PATCH vers /api/v4/users/:id. Un utilisateur authentifié normal pouvait définir "admin": true dans l'objet utilisateur imbriqué et s'escalader en administrateur d'instance GitLab. La vulnérabilité ne nécessitait aucun encodage spécial — une seule propriété imbriquée injectée accordait un accès admin complet. Récompensée de 6 337 $, démontrant l'impact haute sévérité du mass assignment imbriqué dans les plateformes Git auto-hébergées.
L'attaque _destroy: true n'a pas d'équivalent direct dans la plupart des autres langages. accepts_nested_attributes_for de Rails active la suppression des enregistrements associés via l'endpoint de mise à jour du parent par défaut. Les attaquants peuvent énumérer les IDs d'association (souvent séquentiels) et _destroy tous dans une seule requête PATCH — effaçant chaque permission, attribution de rôle ou clé API associée au compte cible. Atténuation : allow_destroy: false (défaut est false — vérifier la config) et restreindre quels IDs l'appelant peut détruire via reject_if.
_destroy sur chaque type d'association.arjun -u "https://cible.com/api/v1/users/me" \
-m PATCH \
--data '{"email": "test@example.com"}' \
--headers "Authorization: Bearer <token>" \
-w nested-mass-assignment-wordlist.txtDictionnaire personnalisé pour les chemins imbriqués :
role_attributes[name]
permissions_attributes[0][name]
organization_attributes[owner_id]
account_attributes[subscription_tier]
profile_attributes[is_admin]# SÉCURISÉ — liste blanche imbriquée explicite
def user_params
params.require(:user).permit(
:email, :first_name, :last_name,
profile_attributes: [:bio, :avatar_url]
# role_attributes PAS autorisé — ne peut pas être défini via mise à jour utilisateur
)
end
# Pour les modèles utilisant accepts_nested_attributes_for :
accepts_nested_attributes_for :profile, reject_if: :all_blank
# PAS : accepts_nested_attributes_for :roleclass ProfileUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = ['bio', 'avatar_url']
# role, is_admin pas dans le sérialiseur imbriqué
class UserUpdateSerializer(serializers.ModelSerializer):
profile = ProfileUpdateSerializer(required=False)
class Meta:
model = User
fields = ['email', 'first_name', 'profile']
# role, permissions, groups PAS dans les champs// SÉCURISÉ — exclure explicitement les champs d'association de la mise à jour
db.Model(&user).Omit("Role", "Permissions", "Organization").Updates(input)// SÉCURISÉ — extraction explicite de champs plats, pas de chemins d'objets imbriqués
const { email, firstName, lastName, bio } = req.body;
await User.findByIdAndUpdate(id, { email, firstName, lastName, bio });
// Pour les sous-documents, utiliser la notation $ explicite :
await User.findByIdAndUpdate(id, {
$set: { 'profile.bio': bio } // uniquement les sous-champs autorisés
});// UserUpdateDto n'a pas de types Role, Permission ou Organization imbriqués
public class UserUpdateDto {
@Email private String email;
private String firstName;
private String lastName;
private ProfileUpdateDto profile; // Seul le DTO profil sécurisé autorisé
}
public class ProfileUpdateDto {
@Size(max = 500) private String bio;
@URL private String avatarUrl;
// is_admin, role PAS présents dans ce DTO
}Le mass assignment par objets imbriqués exploite les associations ORM où une mise à jour du modèle parent peut écrire sur des modèles enfants ou liés via la syntaxe de hachage imbriqué. Dans Rails, accepts_nested_attributes_for permet d'écrire sur role_attributes, permissions_attributes et organization_attributes depuis la mise à jour utilisateur parent. Django et Mongoose ont des patterns analogues avec les sérialiseurs de champs liés et populate.
accepts_nested_attributes_for est une macro Rails qui permet à la soumission du formulaire d'un modèle parent de créer/mettre à jour des enregistrements associés dans la même requête. Quand strong_parameters ne restreint pas la liste blanche des attributs imbriqués (ou utilise permit! sur des hachages imbriqués), un attaquant peut créer des rôles admin, supprimer des permissions existantes ou modifier la propriété d'organisation associée.
Rails 8 a introduit params.expect() comme remplaçant plus strict. Avec la forme mono-bracket params.expect(invoice: [:title, line_items_attributes: [:amount]]), Rails autorise incorrectement un tableau de hachages au lieu d'un seul hachage pour line_items_attributes — permettant l'injection d'attributs sur les modèles line_item imbriqués. La correction est la forme double-bracket : line_items_attributes: [[:amount]].
Les attributs imbriqués Rails acceptent une clé _destroy: true qui supprime l'enregistrement associé. Un attaquant envoyant permissions_attributes: [{id: 1, _destroy: true}, {id: 2, _destroy: true}] peut supprimer tous les enregistrements de permissions d'un utilisateur via l'endpoint de mise à jour parent — un déni de service via suppression en masse.
La méthode populate() de Mongoose résout les références entre documents. Si un gestionnaire de mise à jour passe des chemins de champs contrôlés par l'utilisateur à Model.findByIdAndUpdate() sans restreindre les chemins traversables, un attaquant peut injecter des chemins populate comme noms de champs, atteignant des propriétés sur des modèles liés hors de la portée prévue. CVE-2024-39689 démontre ce pattern.
Les méthodes Save() et Updates() de GORM avec des champs de struct contrôlés par l'utilisateur peuvent mettre à jour les associations imbriquées quand la gestion des associations automatiques de GORM est active. Si la struct passée à Save() contient des champs d'association dérivés de l'input utilisateur, ces associations sont mises à jour. La correction utilise Omit() de GORM pour exclure les champs d'association.
CVE-2024-39689 (Mongoose traversal populate, CVSS 7.5) — les chemins de champs imbriqués atteignaient des associations non autorisées. CVE-2024-43788 (pollution prototype webpack, CVSS 7.3) — traversal __proto__ imbriqué dans les opérations de fusion. Historiquement : GitHub 2012 Homakov a exploité les attributs imbriqués Rails via organization_attributes.
Envoyer des variantes imbriquées des noms de champs privilégiés : user[role]=admin (encodage formulaire Rails), {user: {role_attributes: {name: 'admin'}}}, {user: {permissions_attributes: [{name: 'admin', _destroy: false}]}}, {account: {owner_id: 1}}. Tester à la fois les corps JSON et le format application/x-www-form-urlencoded.
Mongoose supporte les mises à jour de sous-documents avec la notation pointée : 'profile.role': 'admin' met à jour le champ role dans le sous-document profile. Si User.findByIdAndUpdate(id, req.body) est appelé avec un corps contenant des clés à notation pointée, les chemins imbriqués sont traversés et mis à jour. Correction : filtrer toutes les clés contenant un point avant de passer à findByIdAndUpdate.
SQLAlchemy relationships avec cascade='all, delete-orphan' et back_populates peuvent être manipulés si un endpoint FastAPI accepte des modèles Pydantic imbriqués avec extra='allow'. Un attaquant injecte des données de relation imbriquées que SQLAlchemy applique aux modèles associés. Correction : séparer les modèles Pydantic pour chaque niveau de relation, chacun avec extra='forbid'.