Seguridad de NPM: cómo prevenir ataques a la cadena de suministro
8 de noviembre de 2022
0 minutos de lecturaLa seguridad de NPM ha sido un tema de tendencia en los medios en los últimos años, sobre todo en referencia a los paquetes de npm disponibles en el ecosistema, más que al propio registro de npm. El creciente riesgo de seguridad que afecta a los desarrolladores y al software que creamos hace aún más importante entender cómo prevenir ataques a la cadena de suministro y otras vulnerabilidades de seguridad relacionadas con el ciclo de vida del desarrollo de software.
Los ataques a la cadena de suministro han adoptado muchas formas, como los ataques de confusión de dependencias, la introducción de puertas traseras con código malicioso en paquetes de código abierto y el compromiso de la infraestructura de la canalización de compilación. Los riesgos de seguridad presentes en las bibliotecas y los ecosistemas de código abierto representan una amenaza inminente para los desarrolladores.
¿Qué controles de seguridad de software podemos aplicar para mejorar nuestra postura de seguridad? En esta publicación, quiero presentar las prácticas de seguridad de npm y las herramientas de seguridad de la cadena de suministro disponibles para ti como desarrollador de JavaScript (también son bienvenidos los desarrolladores de TypeScript).
El estado actual de los ataques a la cadena de suministro en 2022
Cada vez hay más pruebas de que los desarrolladores desempeñan un papel clave en la seguridad de las aplicaciones. Los desarrolladores están directamente involucrados en el aumento de los incidentes de seguridad, como el módulo peacenotwar, el ataque de confusión de dependencias contra gmx-reference y los ataques de confusión de dependencias de Cobalt Strike, que son solo algunos ejemplos de titulares recientes sobre la seguridad de npm.
¿Cuántas dependencias de npm y cuántos mantenedores distintos crees que tienen tus proyectos? Una investigación académica publicada el 7 de junio de 2019 compartió datos interesantes sobre este tema:
Instalar un paquete promedio de npm implica confiar de forma implícita en 79 paquetes de terceros y 39 mantenedores, lo que crea una superficie de ataque sorprendentemente amplia.
Los actores de amenazas que centran sus esfuerzos en los ataques a la cadena de suministro siempre encuentran formas innovadoras de penetrar en un ecosistema e introducir malware. Un ejemplo destacado es un artículo reciente de investigación sobre seguridad, publicado en 2021, que encontró lo siguiente:
2818 direcciones de correo electrónico de mantenedores asociadas a dominios vencidos, lo que permitiría a un atacante secuestrar 8,494 paquetes al tomar el control de las cuentas de npm. […] Encontramos que el 58.7% de los paquetes y el 44.3% de los mantenedores están inactivos en el registro de npm.
A pesar de estas preocupaciones, el software de código abierto contribuye enormemente a muchas tecnologías innovadoras y ayuda a las organizaciones a acelerar la entrega de resultados de negocio. Aunque el software de código abierto es un gran regalo para el mundo, sigue siendo importante señalar los riesgos de las bibliotecas de software de terceros y el alcance de su impacto cuando se incluyen en nuestras aplicaciones.
¿Qué es la seguridad de la cadena de suministro?
Es bastante común que los desarrolladores piensen en la seguridad de la cadena de suministro de código abierto en términos de puntos de contacto inmediatos, como los paquetes de npm que instalamos (npm install event-stream) e importamos en nuestros proyectos (import colors). Sin embargo, la superficie de ataque de la cadena de suministro de software es mucho más amplia.
¿Qué es un ataque a la cadena de suministro?
En el contexto del desarrollo de software, un ataque a la cadena de suministro ocurre cuando actores de amenazas inyectan malware, puertas traseras u otras cargas maliciosas en componentes de software o infraestructura relacionada con el software, que luego se utiliza para producir otro software funcional. En otras palabras, los actores de amenazas no explotan directamente ni investigan vulnerabilidades en el código fuente del software objetivo.
Los niveles de la cadena de suministro para los artefactos de software, también conocidos como SLSA, ofrecen una buena referencia para identificar puntos de integración débiles donde acechan riesgos. La siguiente representación visual está tomada de el informe técnico de Snyk sobre seguridad de la cadena de suministro:

Si volvemos a los fundamentos de cómo creamos software hoy, podemos ver que muchos de estos hitos conocidos para producir software están expuestos a un panorama de amenazas muy real. De hecho, el siguiente artículo del blog de seguridad de Google presenta varios ejemplos de incidentes de seguridad reales para cada uno de estos hitos de entrega de software.
Seguridad de NPM y medidas preventivas para proteger la cadena de suministro
(1) Prevenir la inyección en archivos lock de NPM
En septiembre de 2019, di a conocer mi investigación de seguridad, que describía los riesgos de seguridad inherentes a los archivos lock de paquetes en el ecosistema de npm. Se descubrió que los administradores de paquetes de JavaScript Yarn y npm eran vulnerables.
La amenaza de seguridad surge cuando actores maliciosos obtienen acceso y la capacidad de contribuir con cambios en el código fuente mediante mecanismos como pull requests, que suelen usarse en GitHub para contribuir a proyectos de código abierto. En ese contexto, si actualizaran un archivo lock como package-lock.json o yarn.lock para incluir una nueva dependencia de paquete npm, o incluso solo modificaran la URL de origen de un paquete existente, cualquier ejecución de la instalación de paquetes (como npm install o yarn install) descargaría ese malware.
La siguiente captura de pantalla muestra cómo inyecté un paquete malicioso mediante un pull request que hice en un paquete de código abierto de npm. Obsérvala con atención y trata de encontrarlo:

Quizás estés repasando la siguiente lista de verificación:
Los paquetes que agregué son conocidos en el ecosistema y no tienen vulnerabilidades: listo.
No hay intentos de typosquatting en los nombres de estos paquetes: listo.
Son versiones válidas de esos paquetes y no son maliciosas por sí mismas: listo.
Aun así, no tendrías suerte al intentar encontrarlo.
Prueba expandir el archivo yarn.lock. Un archivo lock de un administrador de paquetes se genera automáticamente y no está diseñado para que las personas lo lean o mantengan. Por eso es muy difícil encontrar elementos como la siguiente actualización maliciosa que inyecté:

Además, los administradores de paquetes de JavaScript permiten instalar paquetes desde fuentes muy poco habituales, como un gist de GitHub o directamente desde un repositorio de código fuente.
Esto significa que puedo simplemente actualizar el archivo lock para especificar una nueva ubicación de origen (por ejemplo, en la clave resolved) que, como atacante, controlo por completo. También puedo configurar el valor de integridad SHA512 correspondiente para que no se disparen alertas.
Si se combina este pull request, el siguiente colaborador del proyecto que ejecute yarn install obtendrá mi paquete malicioso. Pero, en realidad, ni siquiera es necesario esperar a que un desarrollador interactúe con el proyecto: muchos proyectos de código abierto consolidados tienen un entorno de integración continua (CI).
Es muy probable que, con solo crear este pull request, se inicie una compilación automatizada en GitHub Actions (o quizás en CircleCI). Al compilar este pull request, se ejecutará el proceso de npm install, desde el cual podría acceder a información confidencial y robarla, como los secretos de tu entorno de CI, claves de API, etc.
Para prevenir este tipo de ataque de inyección en archivos lock, puedes usar lockfile-lint. Con lockfile-lint, los desarrolladores pueden analizar sus archivos lock y asegurarse de que estas instantáneas generadas por máquinas de las dependencias fijadas cumplan con una política de confianza específica.
Por ejemplo, puedes configurar lockfile-lint para que falle si encuentra que el archivo lock yarn.lock incluye fuentes de paquetes npm que no se instalarán desde el mirror oficial yarnpkg.org:

En resumen, estas son las medidas preventivas para este posible riesgo de seguridad:
Usa
lockfile-linten tu entorno de desarrollo y en tu CI.No permitas que colaboradores externos actualicen el archivo lock.
Usa bots automatizados para actualizar dependencias. Actualizan el archivo lock de forma automática y confiable (obtienen los paquetes del registro oficial). Consejo: el bot de Snyk lo hace.
(2) Prevenir la ejecución de comandos arbitrarios
Con suerte, no es ningún secreto que ejecutar el comando npm install puede hacer que los paquetes que instalas ejecuten comandos arbitrarios durante el proceso de instalación.
Si en un momento determinado hubieras ejecutado el siguiente comando:
Habrías sido víctima del sabotaje del paquete npm node-ipc, en el que el mantenedor modificó su paquete para ejecutar comandos que crean archivos en la carpeta del escritorio. En otras versiones del paquete npm node-ipc, siguieron un camino más dañino, que incluyó una versión que recorre los archivos del disco duro y los elimina por completo.
Las dependencias pueden ejecutar comandos arbitrarios porque el administrador de paquetes npm proporciona un hook de ciclo de vida install como parte del proceso de instalación de una dependencia. La mayoría de las veces, los mantenedores de módulos lo usan por motivos legítimos, como preparar artefactos de instalación, descargar archivos de recursos o limpiar después de una instalación. También es una forma viable de realizar compilaciones cruzadas para enlaces de módulos nativos que deben compilarse en el host donde se instalan.
La investigación anterior, ¿Cuáles son los eslabones débiles de la cadena de suministro de npm?, a la que hicimos referencia, ofrece información sobre el uso de hooks de instalación en el ecosistema:
Encontramos que el 2.2% (33,249) de los paquetes usan scripts de instalación, lo que indica que el 97.8% de los paquetes podrían seguir la recomendación de npm de no usar el script de instalación como práctica recomendada de seguridad.
En resumen, estas son las medidas preventivas que debes tomar:
Agrega la marca
--ignore-scriptsal comandonpm installpara que omita la ejecución de comandos arbitrarios por parte de las dependencias de paquetes npm.Considera agregar esta marca al archivo de configuración
.npmrcde tu proyecto para proteger también a los demás desarrolladores que trabajan contigo.Ten en cuenta que habilitar esta marca puede impedir que se ejecuten algunos casos de uso legítimos de paquetes npm y provocar que falle el proceso de npm install.
Ten presente que la marca
--ignore-scriptsno se aplica a los paquetes instalados desde fuentes de archivos locales ni cuando se instala un paquete npm desde un repositorio de git.
(3) Evitar las actualizaciones de paquetes de NPM a ciegas
Algunos desarrolladores pueden actualizar explícitamente todas sus dependencias a las versiones más recientes como parte de un proceso de integración continua (CI). Lo hacen junto con la ejecución de pruebas y casos de prueba de ruta feliz, con el objetivo de garantizar que sus aplicaciones sigan funcionando según lo esperado incluso cuando sus dependencias publiquen nuevas versiones. Es una forma de garantizar la compatibilidad con versiones futuras sin interrumpir el funcionamiento de una aplicación.
Puedes actualizar a la versión más reciente de npm ejecutando:
O con el paquete npm específico para eso:
Aunque las razones para hacerlo son legítimas, estas llamadas actualizaciones de paquetes npm a ciegas también pueden provocar incidentes de seguridad. Como acabamos de ver, ejecutar npm install puede ser bastante peligroso.
Actualizar tus dependencias a ciegas implica un riesgo de seguridad inherente y te expone innecesariamente a amenazas como los ataques de confusión de dependencias y otros incidentes que hemos conocido en los últimos años, como el incidente de seguridad de colors o el incidente de seguridad de node-ipc.
Estas son las medidas preventivas que debes tomar para mantener tus dependencias actualizadas:
Usa una herramienta inteligente y automatizada para actualizar dependencias. Estas herramientas suelen incluir funciones para actualizar automáticamente los manifiestos y archivos lock.
Revisa cuidadosamente el registro de cambios y los artefactos de la versión. Asegúrate de que el mantenedor no haya cambiado o de que haya un buen motivo para el cambio. Ten en cuenta que la versión recién publicada también debe tener su código fuente disponible.
Es especialmente importante señalar las actualizaciones automatizadas y el riesgo de descargar un paquete npm malicioso. Snyk no recomienda actualizar a versiones con menos de 21 días de antigüedad. Esto ayuda a evitar versiones que introducen errores funcionales y luego se retiran, o versiones publicadas desde una cuenta comprometida (cuando el titular perdió el control de la cuenta ante alguien con intenciones maliciosas).
Obtén más información en la documentación de Snyk para actualizar dependencias con PR automatizados.
(4) Evita la confusión de dependencias
La confusión de dependencias es un tipo de ataque en el que se crea un paquete para uso interno de una organización, en algún proxy o servicio de alojamiento privado, pero el nombre del paquete sigue disponible para registrarse en el registro de npm.
Por desgracia, es fácil que las herramientas locales estén mal configuradas. Esto, junto con la forma en que el administrador de paquetes npm obtiene los paquetes, crea la siguiente situación: cuando existe un paquete con el mismo nombre en el registro público de npm y hay una versión superior a la instalada, npm la instalará.
Si un empleado de Microsoft no hubiera tenido cuidado, o si su administrador de paquetes npm y su servidor proxy interno hubieran estado mal configurados, se habría producido el siguiente ataque de confusión de dependencias, capaz de infiltrarse en la infraestructura interna de la empresa:
De hecho, el mismo riesgo se aplica al caso en que los empleados ejecutaran este comando confiable para actualizar paquetes npm:
La razón está en la forma en que funcionan los ataques de confusión de dependencias. Aunque esta técnica se divulgó como investigación pública en 2021, el equipo de investigación de seguridad de Snyk encontró evidencia de actores de amenazas que atacan la infraestructura de empresas con este método. Además, Nishant Jaint, investigador de seguridad, describió otro posible vector de ataque para la confusión de dependencias mediante el uso de alias de paquetes npm.
Para detectar y prevenir ataques de confusión de dependencias, puedes usar snync, una herramienta gratuita y de código abierto de Snyk. Este es un ejemplo de cómo ejecutarla:
(5) Seguridad de NPM: protección proactiva contra el malware
Es muy probable que hayas ejecutado el comando npm install para instalar un paquete npm y hayas recibido un resultado como el siguiente:
El problema con lo anterior es que, aunque se encontraron vulnerabilidades de seguridad en los paquetes, solo te enteraste después de instalarlos. ¿Qué contiene el paquete npm amp-html? Antes de entrar en eso, exploremos una mejor manera de instalar paquetes y recuperar el control.
Imagina que pudieras recopilar información útil sobre el estado de los paquetes de tus dependencias antes de instalarlas y luego tomar una decisión informada: agregar ese paquete npm a tu proyecto o «seguir buscando» en npmjs.org.
Aquí quiero presentarte un proyecto de código abierto mío llamado npq. Con npq, puedes establecer medidas de seguridad proactivas y decidir con información si instalar un paquete, según indicadores como si el proyecto tiene un archivo README, cumple con los requisitos de licencias o cuenta con un repositorio de GitHub asociado.
La herramienta npq también está totalmente integrada con Snyk. Si creas una cuenta gratuita, detectará el token de la API de Snyk disponible en tu entorno y consultará la Snyk Vulnerability Database.
En mi entorno de desarrollo, creé un alias para que el comando npm ejecute npq, de la siguiente manera:
Ahora, cada vez que ejecuto npm install, en realidad se llama a npq para realizar primero sus comprobaciones y validaciones. Si decido continuar, npq devuelve el control a la CLI del administrador de paquetes npm para proseguir con la instalación:

También puedes instalar npq y usarlo como herramienta puntual para comprobar si hay vulnerabilidades:
Otra forma de evaluar el estado de las dependencias de paquetes npm es usar Snyk Advisor, una práctica herramienta para buscar paquetes npm, alternativas o paquetes similares, y conocer su estado de mantenimiento y seguridad.
Así se mostraría el paquete npm amp-html en Snyk Advisor:

(6) Ataques de código troyano
Ataques de código troyano es el nombre de un artículo de investigación de seguridad publicado recientemente que analiza el riesgo de las vulnerabilidades invisibles en el código fuente.
Aquí tienes un fragmento de código de un proyecto backend de Node.js que gestiona parte del acceso de administrador aprovisionado a una ruta específica. En realidad, contiene un ataque de código troyano. ¿Puedes detectar el problema?
Activaré el resaltado de sintaxis de JavaScript en mi IDE para que sea más fácil verlo. Aquí tienes una captura de pantalla del fragmento anterior:

Presta atención a la segunda condición relacionada con Debug login for issue #1812. Para entender mejor qué sucede, migremos ese código a un código de Node.js que se pueda ejecutar:
Si ejecutaras este código con node test.js, se imprimiría el siguiente resultado:
Pero ¿por qué? Me imagino que ya te estás rascando la cabeza.
La razón de fondo es que el texto del fragmento de código fuente anterior incluye caracteres de control ocultos a simple vista, pero que cambian profundamente el texto real del código fuente. Estos caracteres invierten efectivamente el orden del texto, de izquierda a derecha y de derecha a izquierda. Así, cuando un intérprete o compilador lo lee, le dan un significado distinto al que percibe el ojo humano, que no los detecta.
¿Cómo pueden los desarrolladores detectar este tipo de problema y evitar que pase inadvertido durante las revisiones de código cuando alguien contribuye código a su proyecto?
Si usas Snyk Code para monitorear tu código fuente, te avisará cuando detecte vulnerabilidades de seguridad, como inyecciones SQL o inyecciones de código inseguras. Además, Snyk Code también informa sobre problemas de código troyano:

Snyk también te ofrece una visualización rápida de la vulnerabilidad de código troyano, junto con las demás vulnerabilidades que encuentra en tu proyecto, en la página de resumen del proyecto:

Por suerte, Visual Studio Code y la plataforma GitHub ya incorporaron señales visuales que te alertan cuando ves archivos de código fuente que contienen caracteres de control invisibles.
Otra herramienta de código abierto que ayuda a detectar y mitigar ataques de código troyano en bases de código JavaScript con ESLint es el complemento de ESLint eslint-plugin-anti-trojan-source.
Seguridad de NPM: ¿qué sigue?
Los riesgos de seguridad de la cadena de suministro de software aumentan constantemente y, por si eso no fuera suficiente motivo de preocupación, los ataques se han perfeccionado para dirigirse a los desarrolladores y sus ecosistemas.
Te recomiendo leer estos artículos para informarte mejor sobre las prácticas recomendadas de seguridad de npm y temas relacionados:
Cómo prevenir paquetes maliciosos y ataques a la cadena de suministro con Snyk, de Daniel Berman.
¿Qué es una puerta trasera? Vamos a crear una con Node.js, de Ulises Gascón
10 prácticas recomendadas de seguridad para npm, de Liran Tal y Juan Picado.
Si quieres aprender sobre prácticas de programación segura, también te recomiendo tomar una de las lecciones prácticas de seguridad de JavaScript en Snyk Learn. ¡Son breves y totalmente gratuitas!
¿Los paquetes NPM pueden ser maliciosos?
La desafortunada realidad es que los paquetes npm pueden ser maliciosos y usar distintas técnicas, como los ataques de typosquatting o la ingeniería social para insertar una puerta trasera en event-stream, para introducir código malicioso que se ejecuta en el entorno del desarrollador. De hecho, algunos paquetes maliciosos fueron obra de sus propios mantenedores, que sabotearon sus paquetes npm con código malicioso, como ocurrió con node-ipc.
¿Cómo puedes comprobar la seguridad de los paquetes NPM?
Snyk encuentra e informa periódicamente sobre paquetes npm maliciosos, como demuestra el descubrimiento de más de 200 paquetes npm maliciosos. Para comprobar la seguridad de los paquetes npm, puedes usar Snyk Advisor para evaluar el estado de los paquetes de código abierto, o usar la CLI de Snyk gratuita y la integración con repositorios para analizarlos y monitorearlos en busca de paquetes maliciosos.
¿Qué daños pueden causar las vulnerabilidades de NPM?
Las vulnerabilidades de seguridad que se encuentran en los paquetes npm pueden afectar tu aplicación y exponerte a riesgos considerables. Si usas una versión sin parches del paquete npm websocket-extensions, eres vulnerable a ataques de denegación de servicio mediante expresiones regulares. Del mismo modo, si usas una versión sin parches del paquete npm st para alojar y servir archivos estáticos con tu aplicación Node.js, eres vulnerable a ataques de recorrido de directorios.
A los desarrolladores les encanta. Los equipos de seguridad confían en él.
Las herramientas de Snyk, diseñadas primero para desarrolladores, ofrecen seguridad integrada y automatizada que satisface tus necesidades de gobernanza y cumplimiento.
