Manipulation de type : échapper aux sandboxes des moteurs de templates
21 mars 2017
0 minutes de lectureL’une des principales caractéristiques des langages interprétés comme JavaScript et Ruby est le typage dynamique, où les types des variables sont déterminés et modifiés à l’exécution. Le typage dynamique a ses inconvénients, mais il peut rendre les logiciels plus flexibles et accélérer le développement. Malheureusement, il ouvre la voie à un vecteur d’attaque appelé manipulation de type, dans lequel les attaquants tentent de modifier le type d’une variable pour déclencher un comportement inattendu.
Cet article est le premier d’une série consacrée à la manipulation de type. Chaque article présentera une ou plusieurs vulnérabilités réelles rendues exploitables par la manipulation des types et expliquera comment elles auraient pu être évitées. Ces exemples concrets ont pour but de vous sensibiliser à des vulnérabilités spécifiques présentes dans des packages que vous utilisez peut-être, mais aussi de vous aider à tirer les leçons de ces erreurs pour les éviter dans votre propre code.
Dans cet article, nous allons voir comment manipuler les types pour contourner les sandboxes des frameworks de templates. Ces templates intègrent souvent des mécanismes de protection, mais, comme nous allons le voir, ils ne sont jamais infaillibles. Nos exemples s’appuient sur des vulnérabilités des plateformes Dust.js de LinkedIn et Nunjucks de Mozilla, avec une brève mention d’Angular.
Vulnérabilité d’exécution de commandes à distance dans LinkedIn Dust.js
Notre principal exemple est une vulnérabilité de Dust.js, un framework de templates populaire publié et utilisé par LinkedIn. Dust.js est également utilisé par PayPal, où un chercheur a découvert une vulnérabilité d’exécution de commandes à distance exploitée par manipulation de type.
La vulnérabilité a été divulguée le 9 janvier 2015 et corrigée dans la version 2.6.0, publiée le 14 septembre 2016. Si vous utilisez Dust.js, n’oubliez pas de vérifier si vos applications utilisent la version vulnérable.
Dépendance explicite au type
Comme la plupart des bibliothèques de templates, Dust.js prend en charge les conditions dans ses templates, comme dans l’exemple suivant, qui utilise le paramètre device pour choisir le code HTML à générer :
Ces conditions sont ensuite évaluées à l’aide de la fonction eval(), qui permet facilement de prendre en charge des conditions très complexes dans le template. Voici un extrait de la fonction if de Dust.js. On voit que les params fournis par l’utilisateur et les cond statiques fournis par le développeur sont combinés pour créer la valeur à évaluer :
Comme la condition peut contenir des données saisies par l’utilisateur (par exemple, le paramètre device), Dust.js utilise une fonction de nettoyage pour empêcher l’évaluation de valeurs de paramètres malveillantes en tant que code. Voici un extrait de cette fonction :
Le code de nettoyage est clair et robuste : il détecte tous les caractères actuellement connus pour permettre de sortir de la chaîne entre guillemets. On remarque également que l’appel HCHARS.test(s) ne s’exécute que sur des chaînes, probablement pour éviter les erreurs lorsque la valeur est indéfinie.
Cette vérification de type évite peut-être les erreurs, mais elle ouvre la voie à une attaque par manipulation de type. Si l’attaquant peut forcer s à être un tableau (ou tout autre objet qui n’est pas une chaîne), il peut contourner complètement la vérification. Plus loin dans le code, le tableau est implicitement converti en chaîne lorsque la condition est transmise à eval, ce qui permet l’exécution de code JavaScript à distance.
De la chaîne au tableau
L’étape suivante pour l’attaquant consiste à tenter de manipuler le type de la variable. Dans les applications pilotées par API, les attaquants peuvent essayer de modifier le type en altérant une charge utile JSON, comme nous l’avons vu avec la vulnérabilité Buffer de Mongoose. Ici, puisqu’il s’agit d’une plateforme de templates web, nous allons nous intéresser à la manipulation de type via qs.
Le package qs est le package JavaScript le plus utilisé pour analyser les chaînes de requête. Il est utilisé par défaut dans express, request et d’autres packages populaires. qs convertit une chaîne de requête en objet JavaScript, ce qui facilite son exploitation. Voici quelques utilisations courantes de qs :
Comme vous pouvez le constater, qs détermine le type de l’objet créé à partir de la manière dont il apparaît dans la chaîne de requête. Si un même nom apparaît plusieurs fois, il est représenté sous forme de tableau. De même, si un paramètre est explicitement indiqué comme étant un tableau, il conservera ce type lors de l’analyse.
Il ne reste qu’un petit pas à franchir pour exploiter la vulnérabilité de Dust.js. Avec le template ci-dessus, qui était effectivement utilisé par PayPal, il suffit à l’attaquant de fournir deux fois l’argument device, ou de le transmettre sous la forme d’un paramètre device[]=value. La variable est alors transmise sous forme de tableau, ce qui contourne la logique de nettoyage.
Exploitation postérieure à la compromission de Dust.js
Même si cela s’écarte un peu du sujet de la manipulation de type, il est intéressant de voir comment un attaquant peut exploiter cette vulnérabilité, notamment au moyen des actions dites de « post-exploitation ».
Pour commencer, l’attaquant doit parvenir à exécuter du code. Pour cela, il peut fournir une URL comme celle-ci :
Afficher un message dans la console n’est pas très utile pour un attaquant. Il peut donc fournir un script plus élaboré, par exemple pour exfiltrer des informations. Dans le cas de l’exploitation chez PayPal, le chercheur a utilisé une charge utile de ce type :
Ce qui se traduit par un appel à eval comme celui-ci :
L’exploit postérieur à la compromission utilise le module child_process pour exécuter localement une commande curl et envoyer le fichier /etc/passwd à l’attaquant. Avec une charge utile légèrement plus longue, cela aurait aussi pu être fait à l’aide d’une commande Node native. Toutefois, l’utilisation de child_process fait passer la vulnérabilité de l’exécution de code JavaScript à distance à l’exécution de commandes shell à distance, élargissant ainsi les possibilités de l’attaquant.
Options de correction
La plupart des attaques par manipulation de type — et celle-ci ne fait pas exception — peuvent être contrées en interdisant, en normalisant ou en traitant spécifiquement le type de la variable. Ces trois options peuvent être combinées pour mettre en place plusieurs niveaux de défense.
Interdire consiste à n’accepter que certains types. Dans ce cas, Dust.js aurait pu interdire les paramètres qui ne sont pas des chaînes dans ses templates. Cela réduirait les fonctionnalités, et la pertinence de cette approche dépend donc de l’utilisation du framework.
Normaliser consiste à convertir différents types d’entrée en un seul type. Dans ce cas, Dust.js convertissait implicitement les variables concernées en chaînes au moment de les transmettre à la fonction eval(). Plutôt que d’effectuer la conversion à ce stade, des fonctions appelées plus tôt pourraient convertir en chaîne les tableaux, les entiers et les autres types reçus, puis poursuivre le traitement — y compris le nettoyage — en sachant que seules des chaînes sont acceptées.
Enfin, le traitement spécifique consiste à gérer explicitement chacun des types acceptés. Dans ce cas, Dust.js aurait pu détecter les tableaux et normaliser chaque caractère qu’ils contiennent, tout en tenant compte des méthodes dangereuses permettant de combiner plusieurs éléments du tableau. C’est l’option la plus fragile des trois, mais elle prend aussi en charge des entrées plus complexes.
Dust.js a choisi une méthode de traitement spécifique visant à détecter tous les objets dotés d’une fonction toString. Cette solution règle le problème, mais rend le code quelque peu fragile, car le code en aval peut choisir de convertir un tableau en sortie destinée à l’utilisateur d’une autre manière. Voici l’extrait pertinent du correctif de Dust.js :
Si vous utilisez une version vulnérable de Dust.js et ne pouvez pas la mettre à niveau, vous pouvez normaliser dans votre code les types des entrées transmises à Dust.js ou interdire les types autres que les chaînes afin de bloquer les tableaux malveillants. Vous pouvez vérifier si vous utilisez une version vulnérable à l’aide de la CLI ou de l’intégration GitHub de Snyk.
Vulnérabilité XSS dans Mozilla Nunjucks
La vulnérabilité de Dust.js n’est pas un cas isolé : elle illustre un schéma récurrent. Examinons par exemple une possibilité similaire de manipulation de type qui existait dans un autre framework de templates, la bibliothèque Nunjucks de Mozilla. Cette vulnérabilité a été divulguée le 6 septembre 2016 par Matt Austin et corrigée dans la version 2.4.3, publiée le 9 septembre 2016. Comme pour Dust.js, vous pouvez utiliser Snyk pour vérifier si vos applications utilisent une version vulnérable de Nunjucks.
Comme Handlebars, Mustache et d’autres frameworks, Nunjucks permet d’indiquer des noms de variables entre deux accolades afin que leur valeur soit encodée en HTML. Voici un exemple de cette protection en action :
Toutefois, comme Dust.js, la fonction de nettoyage de Nunjucks n’échappait que les paramètres de type chaîne. Voici un extrait du code de nettoyage :
En suivant une logique similaire à celle décrite ci-dessus, une URL de ce type (analysée par qs) :
Provoquera un appel et une sortie XSS de ce type :
Comme vous pouvez le voir, le problème suit le même schéma : la possibilité pour l’attaquant de manipuler le type n’a pas été anticipée. L’attaquant peut donc contourner le nettoyage et injecter du code malveillant.
Pour corriger le problème, comme dans le cas de Dust.js, Nunjucks aurait pu interdire les entrées sous forme de tableau, les normaliser en chaînes ou appliquer un nettoyage spécifique aux tableaux. Comme le montre ce correctif, Mozilla a choisi de normaliser les entrées sous forme de tableau à l’aide de la méthode toString.
En résumé
La manipulation de type est un vecteur d’attaque peu connu, mais elle représente un réel danger pour tous les langages à typage dynamique. Dans ces langages, il faut apprendre à considérer le type comme une autre forme d’entrée, et appliquer à chaque variante une liste d’autorisation, une normalisation ou un traitement spécifique.
Dans cet article, nous avons vu comment la manipulation de type pouvait permettre de sortir de la sandbox d’un framework de templates, un problème qui a piégé de nombreux frameworks. Il est extrêmement difficile de créer une sandbox sans contrôler l’environnement d’exécution. C’est pourquoi Angular a fini par abandonner complètement cette approche.
Dans les prochains articles, nous verrons d’autres situations où la manipulation de type peut intervenir, afin de vous aider à mieux comprendre les risques qu’elle présente et la manière dont elle peut se manifester.


