Por qué los archivos de bloqueo de npm pueden ser un punto ciego de seguridad para inyectar módulos maliciosos
24 de septiembre de 2019
0 minutos de lecturaHace poco empecé a explorar la idea de modelar amenazas en los paquetes del ecosistema npm. ¿Podría volver a ocurrir un incidente de event-stream? ¿Y qué hay de otros ataques a la cadena de suministro? ¿Cuál será el próximo vector de ataque que aún no hemos visto y podría ser completamente prevenible?
Y entonces, un día, ¡se me ocurrió una idea! Déjame mostrarte lo fácil que es introducir puertas traseras que los propietarios de proyectos pueden pasar por alto… y dejar tu código vulnerable.
La siguiente imagen muestra cómo inyecté un paquete malicioso mediante un pull request que hice en un paquete de código abierto de npm.
Míralo con más atención y ve si puedes detectarlo:

Ahora, así se ve mi lista de verificación para revisar pull request cuando la aplico a este PR:
Los paquetes que agregué son conocidos en el ecosistema y no tienen vulnerabilidades: listo.
Los nombres de estos paquetes no intentan suplantar otros mediante errores tipográficos: listo.
Son versiones válidas de esos paquetes y no son maliciosas por sí mismas: listo.
Se ve bien. ¡Adelante, hagamos merge!
Pero espera. ¿Revisaste ese archivo de bloqueo con atención? Claro que no, y no te culpo. No es nada obvio, por estas limitaciones:
La interfaz de GitHub oculta de forma predeterminada las diferencias cuando superan un par de cientos de líneas de código.
¿Para qué revisar este archivo? Se genera automáticamente y tampoco es muy fácil de leer.
A pesar de estas limitaciones, ¿qué podría salir mal?
Expandamos las diferencias y veamos qué contienen:

Cuando hay un archivo de bloqueo, ya sea el yarn.lock de Yarn o el package-lock.json de npm, durante la instalación se consulta el archivo como fuente principal de verdad para determinar las versiones de los paquetes y sus fuentes al instalar las dependencias.
¿Ves a qué me refería? Un cambio de 500 líneas en un archivo de bloqueo es, en realidad, una cantidad bastante pequeña. He visto miles de líneas de cambios en un package-lock.json de npm o en un archivo yarn.lock de Yarn. En serio, ¿quién realmente revisa miles de líneas de cambios de caracteres? Para empeorar las cosas, en este archivo de bloqueo vimos cambios en las versiones de las dependencias y actualizaciones de metadatos: en general, nada fuera de lo común.
Si bajamos un poco para revisar el resto de los cambios aplicados a este archivo, encontramos esta joya:

Al mirar más de cerca, queda claro que reemplacé el paquete ms original de npm para resolverlo con mi propia versión, alojada en mi repositorio de GitHub. Debería haberlo obtenido del registro oficial de npm, tal como se había configurado originalmente en el archivo de bloqueo del proyecto.
Este proyecto usa varias versiones de ms, pero solo cambié ms@2.1.1 para usar mi propia versión personalizada:
Es muy difícil acordarse de revisar con tanto detalle, ¡y no es de extrañar que tan pocas personas revisen los cambios de los archivos de bloqueo carácter por carácter!
Entonces, ¿qué hace mi propia versión de ms@2.1.1?
Cuando se integre este pull request, inyectaré mi versión maliciosa de ms@2.1.1 en el código para controlar su comportamiento durante la ejecución.
De esta manera, podría introducir una puerta trasera, alterar la lógica del módulo ms o ejecutar scripts de postinstalación. Además, la URL de alojamiento https://github.com/lirantal/ms/tarball/master es claramente sospechosa para este experimento, pero ¿te resultaría fácil detectarla si comprara un dominio con errores tipográficos para alojarlo en https://registry.npmgs.org/ms/ ?
Para este experimento, mi paquete alojado de ms tiene un script de postinstalación sencillo que muestra un texto y escribe en un archivo. Así, si instalaras el paquete, podrías confirmar que el evento posterior a la instalación se ejecuta simplemente al ejecutar yarn install:
En las aplicaciones basadas en proyectos, la seguridad de los archivos de bloqueo es fundamental y está expuesta al tipo de ataques de inyección maliciosa que se describen en este artículo.
Sin embargo, los archivos de bloqueo que se incluyen en paquetes de bibliotecas están diseñados exclusivamente para usarse en el flujo de trabajo de desarrollo del propio paquete. Por eso, cuando el proyecto (paquete) se instala como dependencia en otro proyecto, no se usa su archivo de bloqueo. Esto significa que, en el caso de las bibliotecas, la inyección en archivos de bloqueo afectaría principalmente a quienes mantienen el proyecto y a sus colaboradores, y los pondría en riesgo.
¿Qué podemos hacer para mejorar la seguridad de los archivos de bloqueo?
Como los archivos de bloqueo están pensados para que los lean las máquinas, es fácil procesarlos automáticamente. Por eso, son el trabajo perfecto para un linter y algunas políticas predefinidas.
Como medida de protección contra el vector de ataque anterior, creé lockfile-lint para ayudar con esta tarea. Agregué este paso breve al análisis estático de código, que puedes ejecutar en un servidor de CI como Travis CI o Circle CI.
Como mínimo, debes validar que todos los recursos se sirvan:
por HTTPS
desde una fuente confiable
Por ejemplo, ejecutar esas verificaciones en CI para un proyecto de Yarn es muy sencillo:
Aquí tienes un ejemplo de errores comunes que se detectan al ejecutar el linter en el archivo de bloqueo de un proyecto:

Resumen
Los ataques pueden adoptar muchas formas y tipos… ¡incluso pueden provenir de archivos de bloqueo!
Con el incidente de event-stream, vimos el primer ataque de ingeniería social exitoso contra quienes mantienen proyectos de código abierto. Quizás fue uno de los ataques más sofisticados contra la seguridad de la cadena de suministro de software que se habían visto hasta entonces en el ecosistema de npm y JavaScript.
La inyección en un archivo de bloqueo podría atacar de manera similar, por lo que debes adoptar prácticas adecuadas para mitigar este problema o, al menos, reducir el riesgo. Considera lo siguiente:
Revisa cuidadosamente los cambios en los archivos de bloqueo (lockfile-lint puede ayudarte).
Permite que solo quienes mantienen el proyecto de forma activa, y no colaboradores esporádicos, realicen cambios en los archivos de bloqueo.
Evita usar archivos de bloqueo en las bibliotecas.
Si te interesa leer más sobre los archivos de bloqueo y cómo se usan, escribí en detalle sobre cómo entender los archivos de bloqueo de paquetes en el ecosistema npm.
Empieza con Capture the Flag
Aprende a resolver desafíos de Capture the Flag viendo nuestro taller virtual introductorio a pedido.


