La reparación de vulnerabilidades indirectas es una de esas tareas complejas, tediosas y, francamente, aburridas que nadie quiere tocar. Nadie excepto por desmantelado, parece. Claro, hay muchas formas de hacerlo manualmente, pero ¿se puede hacer automáticamente con un riesgo mínimo de romper los cambios? El equipo Debricked decidió averiguarlo.
Un bosque lleno de árboles frágiles
Entonces, ¿por dónde empiezas?
En primer lugar, debe haber una forma de solucionar la vulnerabilidad, que, para las dependencias indirectas, no es un paseo por el parque. En segundo lugar, debe hacerse de manera segura o sin que se rompa nada.
Verá, las dependencias indirectas se introducen en lo más profundo del árbol de dependencias y es muy complicado llegar a la versión exacta que desea. Como dijo una vez el director de I+D de Debricked, “Está girando las perillas jugando con sus dependencias directas y rezando a Torvalds para que se resuelvan los paquetes indirectos correctos. Cuando Torvalds está a tu favor, tienes que sacrificar algo de almacenamiento en la nube al tío Bob para asegurarte de que las actualizaciones no rompan tu aplicación..”
En otras palabras, realmente debería haber una manera más fácil y menos estresante de hacerlo.
En este artículo, lo guiaremos a través de cómo la resolución de vulnerabilidades transitivas se puede hacer manualmente y, hacia el final, le mostraremos la solución Debricked, que le permite hacerlo automáticamente. Si realmente solo está interesado en la solución, le sugiero que comience a desplazarse.
Cirugía de precisión en tu árbol de dependencia
Durante la fase de investigación del proyecto de base de datos de grafoso, cómo Debricked corrige hoy sus vulnerabilidades de código abierto a la velocidad de la luz, el equipo descubrió algunos artículos explicando cómo solucionar vulnerabilidades indirectas en NPM.
Como se indica en el artículo, el paquete “minimista” se ve afectado por vulnerabilidades, a saber CVE-2021-44906 y CVE-2020-7598.
Ambas son vulnerabilidades de “contaminación de prototipos”, lo que significa que los argumentos no se desinfectan adecuadamente. Afortunadamente, los mantenedores de `minimist` corrigieron estas vulnerabilidades en la versión 1.2.6.
Desafortunadamente, `mocha` versión 7.1.0 resuelve `minimist` 0.0.8, que está dentro del rango vulnerable de estas vulnerabilidades. Como sugiere el autor de Este artículoestas vulnerabilidades se pueden corregir de diferentes maneras.
¡Pero! ¿Qué pasa con los cambios de ruptura?
La primera sugerencia es simplemente desencadenar una actualización de todas las “dependencias indirectas”, lo que significa que en realidad no cambiaremos la versión de `mocha`. Para realizar esta actualización, simplemente ejecute `npm update`, elimine su archivo `npm.lock` y ejecute `npm install`. Esto regenera el árbol de dependencias con la última versión posible (según las restricciones) de sus dependencias indirectas. Con este método, el riesgo de interrumpir los cambios es muy bajo, ya que en realidad no actualiza ninguna de sus dependencias raíz, solo las indirectas.
Los cambios importantes ocurren cuando la funcionalidad o la interfaz del paquete no es compatible con versiones anteriores, lo que significa que una actualización del paquete podría causar que su aplicación se rompa. Los cambios de última hora comunes son la eliminación de clase/función, el cambio de argumentos a una función o el cambio de licencia (¡cuidado con eso!).
Pero la vida no siempre es tan fácil, y esta simple actualización del árbol no resolverá la vulnerabilidad. El problema es que `mkdirp` ha bloqueado su versión de `minimist` a 0.0.8. Esto significa que los contribuyentes de `mkdirp` llegaron a la conclusión de que no son compatibles con las versiones más nuevas de `minimist`, y forzar la actualización de `minimist` puede introducir cambios importantes entre `mkdirp` y `minimist`.
Piensa… ¡gráficos!
Entonces, la pregunta del millón es: ¿qué versión de `mocha` se debe usar, que a su vez se convierte en una versión segura de `minimist` sin romper el árbol de dependencias? Esto es en realidad un problema gráfico, que se ha descrito en Este artículo.
¿Qué algoritmo gráfico resolvería este problema? La forma en que NPM resuelve las dependencias puede ser un poco complicada, ya que se les permite “dividir” el árbol de dependencias. Esto significa que pueden tener múltiples versiones de una dependencia para asegurarse de que siempre tengamos un árbol que sea compatible. Para resolver la vulnerabilidad, debemos asegurarnos de que todas las instancias de `minimist` sean seguras actualizando todas las raíces que pueden filtrarse a `minimist`.
El algoritmo utilizado para resolver este problema se llama “All Max Paths Safe”. Recorriendo el gráfico de dependencias y manteniendo las versiones máximas, mientras eliminamos todas las demás versiones de ese paquete en cada intersección, podemos crear una representación aproximada de nuestro árbol de dependencias. Si la aproximación es segura, ¡eso significa que nuestro árbol real también lo será!
Al realizar este algoritmo para todas las versiones potenciales de `mocha`, encontramos la actualización más pequeña para corregir esta vulnerabilidad. Para obtener la velocidad que queríamos para este algoritmo, el equipo tuvo que crear un algoritmo personalizado Procedimiento Neo4j, que puede gestionar la búsqueda de más de 100 versiones raíz con una profundidad de búsqueda de más de 30 en ~150 milisegundos. Rápido, ¿eh?
En este caso, no tenemos que buscar muy lejos… ¡ya que 7.1.1 de `mocha` es seguro! Esta es solo una actualización de parche, lo que indica que el riesgo de romper los cambios es muy bajo. Para casos menos complejos (como este ejemplo), ‘npm audit’ puede ayudarlo con su fantástico comando ‘npm audit fix’.
¡No sea ad-hoc, entre en la forma pub-sub-humana de trabajar!
Ahora, si llegaste hasta aquí (felicidades, muy impresionante) y pensaste, “esto suena realmente complejo y requiere mucho trabajo”, no te preocupes, no eres el único. Por suerte, todo esto sucede de forma completamente automática en la herramienta Debricked al hacer clic en este pequeño botón:
A partir de ahora, esto está disponible para Javascript. Pronto, el soporte se extenderá a Java, Golang, C#, Python y PHP.
Si aún no eres un desmantelado Usuario, ¿qué estás esperando? Es gratis para desarrolladores individuales, equipos más pequeños y proyectos de código abierto (y si es una organización más grande, no se preocupe. Hay una prueba gratuita generosa). Regístrate gratis aquí.