Qu’est-ce qu’une vulnérabilité de type Mass Assignment ?

Pour faciliter la tâche aux développeurs, de nombreux frameworks intègrent des fonctionnalités qui associent automatiquement les paramètres d’une requête HTTP à des variables liées à un objet dans le code de l’application.

Une vulnérabilité de type Mass Assignment survient lorsque le serveur ne filtre pas correctement les données transmises par l’utilisateur et les associe directement à un objet sans vérification.

De cette manière, un attaquant peut ajouter de nouveaux paramètres dans la requête HTTP, qui vont créer ou remplacer des variables dans le code de l’application alors que cela n’était pas prévu initialement.

Selon les frameworks, on parle aussi de vulnérabilités « autobinding » ou « object injection ».

Impact d’une faille de type Mass Assignment

Le principal impact d’une faille de type Mass Assignment est lié au fait de modifier ou de créer des variables. En fonction des variables ou objets affectés, l’impact peut être plus ou moins important, de la simple modification d’une valeur n’ayant aucun impact à une élévation de privilèges.

En effet, lorsque la vulnérabilité est présente sur une fonctionnalité liée à la création ou la modification d’utilisateurs, il n’est pas rare de pouvoir élever ses privilèges.

Ce fut le cas par exemple sur la plateforme Github en 2012 lorsqu’une vulnérabilité de type Mass Assignment permettait d’uploader sa clé publique SSH vers n’importe quelle organisation entrainant potentiellement une fuite massive de données.

De fait, une telle vulnérabilité n’est pas à prendre à la légère car elle peut avoir un impact sévère.

Comment identifier ce type de vulnérabilité ?

Comme un attaquant ou un pentester ne dispose pas forcément de la liste de toutes les variables et des valeurs associées à l’objet modifié côté serveur, identifier une vulnérabilité Mass Assignment peut être complexe.

En fonction des conditions techniques du pentest, voici quelques pistes pour identifier ce type de faille de sécurité :

  • En boite blanche, c’est à dire avec accès au code source, il est possible de se référer aux modèles de données pour identifier des champs sensibles qui ne seraient pas transmis par défaut dans les requêtes HTTP.
  • Sans accès au code source, cela est plus complexe. Une technique consiste à tester des paramètres connus ou probables. Il s’agit donc de faire du fuzzing sur les paramètres. Par exemple, ajouter un champ « rôle » dans la requête de création d’un utilisateur avec des valeurs standards comme « admin » ou « root » peut conduire à une élévation de privilèges en cas de Mass Assignment.
  • Dans certains cas, avec la documentation de l’API, il est possible d’identifier des paramètres qui ne sont pas utilisés par défaut. Par exemple, avec un accès à l’introspection d’une API GraphQL, donc à la liste des champs associés à une entité, on peut essayer de rajouter une variable dans certaines « queries » ou « mutations » dans l’espoir que cela soit pris en compte.
  • Une autre technique consiste à observer les réponses du serveur. Si des champs supplémentaires sont retournés, il peut être intéressant de les réutiliser dans les requêtes pour détecter un Mass Assignment. Imaginons par exemple que l’on a accès à la liste des utilisateurs. De cette manière, si on accède à la route d’API permettant de lister les utilisateurs, on peut voir dans la réponse du serveur un champ « role » associé à chaque utilisateur. Il peut donc être intéressant d’ajouter ce paramètre rôle avec une valeur valide dans la requête permettant de modifier les informations du profil utilisé ou celle qui permet de créer un utilisateur.

Exploitations courantes de vulnérabilités de type Mass Assignment

Mass Assignment et élévation de privilèges

Lors d’un pentest en boite grise, nous avions accès à une application avec plusieurs niveaux de privilèges pour les utilisateurs. Nous disposions d’un accès utilisateur (non admin), mais permettant néanmoins de créer d’autres utilisateurs du même niveau de privilèges.

En passant par l’interface graphique, la requête d’ajout d’un utilisateur ressemblait à cela :

Le contenu de la réponse du serveur en JSON était le suivant :

Comme nous pouvons le voir, un champ « Profiles » est retourné dans l’objet JSON correspondant au nouvel utilisateur que nous venons de créer. Il semble correspondre aux droits de l’utilisateur. Ce champ a automatiquement été ajouté. Nous pouvons notamment voir que l’utilisateur nouvellement créé a un profil de type « customer » associé à l’identifiant 5. Ce profil possède certains rôles.

Le fait que ce champ « Profiles » soit présent dans la réponse alors qu’il n’a pas été renseigné initialement doit immédiatement nous alerter. Nous pouvons maintenant essayer de rejouer la création d’un utilisateur en rajoutant un champ « Profiles ».

Ici, nous savons que l’identifiant 5 du profil correspond à un utilisateur de type « customer ». Essayons avec l’identifiant 1. Nous avons laissé vides les autres champs, car nous ne connaissions pas les valeurs existantes :

Réponse du serveur :

L’attaque de Mass Assignment a été prise en compte. L’identifiant « 1 » du profil a bien été associé à l’utilisateur que nous venons de créer. La réponse nous indique que nous avons réussi à créer un utilisateur de type administrateur.

Cela veut dire que côté serveur, tout l’objet JSON envoyé par l’attaquant est utilisé pour faire une liaison entre les champs transmis et l’objet utilisateur qui est créé. Cela sans vérifier quelles sont les données transmises et en acceptant un champ non prévu.

Nous pouvons maintenant nous connecter avec le nouvel utilisateur. Nos privilèges ont été élevés et nous pouvons effectuer toutes les actions réservées aux administrateurs.

Mass Assignment sur une API GraphQL et élévation de privilèges

Lors d’un pentest sur une application fonctionnant avec une API GraphQL, une mutation permettait de mettre à jour le profil de l’utilisateur. Par défaut et en passant par l’interface graphique, la mutation transmise était la suivante :

À première vue, rien de particulièrement intéressant sur cette mutation. Elle permet à un utilisateur de changer son nom, son prénom et son numéro de téléphone.

Or, nous avions accès à l’introspection GraphQL (équivalent de la documentation d’une API classique) et nous avons pu y voir que l’objet GraphQL « User » possédait également un champ « role ».

En modifiant la mutation précédente, nous avons réussi à changer le rôle de notre utilisateur en raison d’un Mass Assignment. Nous avons ainsi pu élever nos privilèges :

Requête complète :

Suite à cette requête, notre utilisateur est bien passé administrateur car la mutation prenait en compte le champ rôle même s’il n’était pas envoyé par défaut.

Comment prévenir les failles de type Mass Assignment ?

Heureusement, prévenir ou corriger ce type de vulnérabilité est assez simple.

En effet, il s’agit de mettre en place une liste blanche des champs qui sont autorisés à être utilisés côté serveur pour la création ou la modification d’un objet. Par ailleurs, seuls les champs non sensibles doivent être autorisés. Et si d’autres champs sont présents dans la requête reçue, ils ne doivent pas être pris en compte par le serveur.

Souvent, les frameworks permettent de configurer simplement ce type de liste blanche. Une liste des solutions potentielles existantes par framework est présente sur le site de l’OWASP.

Si nous reprenons les exemples précédents, dans le premier cas, la liste blanche ne doit pas inclure le champ « profiles ». Si un utilisateur tente d’envoyer une valeur pour ce champ, la valeur transmise ne doit pas être prise en compte pour la création de l’utilisateur.

Dans le second cas, la mutation permettant la modification du profil ne doit pas accepter la valeur « rôle ». Elle doit être limitée seulement aux paramètres « first_name »,  « last_name » et « language ».

Auteur : Yoan MONTOYA – Pentester @Vaadata