Exploiter Buffer
5 avril 2016
0 minutes de lectureParmi les merveilles de Node se cache une bombe à retardement du nom de Buffer. Si elle est mal utilisée, cette classe risquée peut facilement exposer la mémoire côté serveur et, avec elle, vos secrets et vos clés, en raison d’une vulnérabilité signalée par Feross Aboukhadijeh et Mathias Buss Madsen. Des projets réputés et populaires sont tombés dans le piège, notamment les packages npm mongoose, ws, request et, plus récemment, sequelize, qui ont introduit des vulnérabilités très répandues.
Dans cet article, nous allons expliquer le fonctionnement de Buffer et les raisons de son comportement. Nous lancerons une attaque contre une application vulnérable afin d’en montrer plus clairement les conséquences. Enfin, nous aborderons les changements à prévoir dans Node 6 et présenterons 5 étapes pour vous protéger du comportement par défaut de Buffer, et
Comment en est-on arrivé là ?
JavaScript côté client nous évite d’avoir à gérer l’allocation de mémoire. Comme dans de nombreux langages comparables, en JS, le moteur sous-jacent (par exemple V8) alloue la mémoire et la libère automatiquement au besoin, ce qui simplifie et sécurise le développement. Dans le navigateur, empêcher l’accès à la mémoire est également nécessaire pour préserver le bac à sable dans lequel s’exécute JS.
Lorsque JS s’est étendu aux serveurs avec Node, le bac à sable du navigateur a disparu et le besoin de traiter facilement et rapidement les données binaires s’est accru. Pour répondre à ces besoins, Node a introduit la classe Buffer, qui gère les données binaires. Notez qu’ES6 a ensuite créé d’autres classes orientées données binaires, notamment TypedArray et ArrayBuffer.
Buffer est un tableau modifiable de données binaires, qui peut être initialisé à partir d’une chaîne, d’un tableau ou d’un nombre. Voici quelques extraits de la documentation de node.js :
Les deux premières variantes créent simplement une représentation binaire de la valeur reçue. La dernière, en revanche, préalloue un buffer de la taille spécifiée, ce qui en fait un buffer utile, notamment pour lire des données depuis un flux.
La faille de sécurité
Avec le constructeur number de Buffer, la mémoire est allouée, mais elle n’est pas remplie de zéros. Le buffer alloué contient plutôt… ce qui se trouvait en mémoire à ce moment-là. Vous pouvez observer ce comportement en exécutant Node dans un terminal, puis en créant à plusieurs reprises un Buffer d’une taille donnée et en constatant que chacun contient des valeurs différentes, puisqu’il pointe vers une autre partie de la mémoire précédemment utilisée.
Ce comportement est bien documenté, et vous pouvez mettre à zéro la mémoire allouée simplement en appelant « buf.fill(0) ». Cependant, si vous oubliez de la mettre à zéro, vous risquez d’exposer de la mémoire. Cette exposition peut sembler anodine, jusqu’à ce qu’on pense au nombre de secrets — clés, code source, informations système — qui pourraient être révélés. Heartbleed, la grave vulnérabilité d’OpenSSL de 2014, était une vulnérabilité d’exposition de mémoire.
Démonstration du risque de sécurité
Pour mieux comprendre la vulnérabilité de Buffer, examinons une application vulnérable appelée Goof. Son code est hébergé sur GitHub, avec des instructions d’installation si vous souhaitez exécuter ces attaques vous-même.

Il s’agit d’une application de liste de tâches très simple (et vulnérable). Elle utilise mongoose pour communiquer avec MongoDB, où les éléments sont stockés, à l’aide d’un schéma simple :
Comme vous pouvez le constater, le champ content, qui contient la tâche proprement dite, est de type Buffer, ce qui permet de prendre en charge les données binaires. Comme de nombreuses applications Node.js, snyk-demo-todo expose également une API JSON, que nous allons utiliser pour créer un élément en ligne de commande :
La valeur de la variable content a été transmise au constructeur Buffer, qui a initialisé une courte chaîne ensuite écrite dans la base de données. Le nouvel élément est visible lorsque nous consultons l’application et figure également dans la réponse (encodé en base64) :

Passons maintenant à l’exploitation de la vulnérabilité. Nous allons envoyer la même requête, mais remplacer la chaîne "Buy Milk" par le nombre non encadré de guillemets 800 :
Comme précédemment, la valeur de content est transmise au constructeur Buffer. Cette fois, toutefois, la valeur est de type number, ce qui déclenche l’autre constructeur Buffer… Celui-ci initialise l’élément de la liste de tâches avec 800 octets de mémoire non initialisée, stockés dans la base de données. Ces données seront de nouveau visibles dans la réponse HTML (bien qu’elles soient binaires et donc difficiles à lire) et seront renvoyées à notre curl après encodage en base64 :

Nous pouvons maintenant répéter cet appel encore et encore pour récupérer davantage de blocs de mémoire et les décoder en base64. Nous supprimerons probablement les notes créées au fur et à mesure pour éviter d’éveiller les soupçons, en examinant les données ou en recherchant des motifs comme secret, ssh-key, du code source, etc.
Comment corriger le problème ?
Dans notre application exemple, la vulnérabilité se trouvait dans la dépendance mongoose. Le correctif consistait à convertir les nombres en tableaux, afin de les traiter comme un nombre unique plutôt que comme une longueur. Si vous utilisez une version vulnérable de mongoose, vous devez effectuer une mise à niveau vers une version plus récente. Si vous ne savez pas si vous l’utilisez, faites appel à Snyk pour détecter et corriger ces vulnérabilités et bien d’autres.
Si la vulnérabilité se trouve dans votre propre code, vous pouvez soit interdire l’utilisation du constructeur number (en le convertissant éventuellement en tableau ou en chaîne), soit utiliser la fonction fill(0) après chaque appel. Notez que la mise à zéro de la mémoire, bien que rapide, prend du temps avec les buffers volumineux. Tenez donc compte de ses répercussions sur les performances de votre application.
Changements à venir dans Node 6
Ce comportement par défaut de Buffer est préoccupant et à l’origine de plusieurs vulnérabilités connues. Cependant, modifier ce comportement dans les versions actuelles de Node est difficile, car cela pourrait casser des applications. À partir de Node 6, il est toutefois recommandé d’utiliser les méthodes explicites alloc et allocUnsafe, qui allouent respectivement de l’espace avec et sans mise à zéro. Cette API explicite peut aider les développeurs à éviter des erreurs similaires.
Le constructeur par défaut restera disponible et fonctionnera de la même manière, mais il sera obsolète et déconseillé. Cela dit, vous pouvez utiliser l’option --zero-fill-buffers de Node (là encore, à partir de Node 6 uniquement), qui fera en sorte que le constructeur numérique par défaut de Buffer mette à zéro le buffer alloué.
5 étapes pour rester en sécurité
Buffer est une classe utile, mais dangereuse. Si vous envisagez de l’utiliser, posez-vous les questions suivantes :
Pouvez-vous utiliser
TypedArrayouArrayBufferà la place ? Ces classes prennent également en charge les données binaires et mettent leur mémoire à zéro.Pouvez-vous interdire le constructeur
number? Si oui, faites-le, comme l’a faitmongoose.Pouvez-vous mettre les données allouées à zéro avec
buf.fill(0)? Cela aura un léger impact sur les performances, mais sera plus sûr.Si vous ne pouvez absolument pas faire tout ce qui précède, suivez attentivement la destination du contenu de
Buffer. Très attentivement.Si vous utilisez des packages npm comme dépendances, exécutez snyk wizard pour corriger les vulnérabilités liées à
Bufferque pourraient présenter vos dépendances.
Lancez-vous dans les challenges Capture The Flag
Apprenez à résoudre des challenges Capture The Flag en regardant à la demande notre atelier virtuel d’initiation.