Redescubrir la inyección de argumentos al usar herramientas de VCS: git y mercurial
Alessio Della Libera
23 de agosto de 2022
0 minutos de lecturaUno de los principales objetivos de esta investigación fue explorar cómo es posible ejecutar comandos arbitrarios incluso al usar una API segura que previene la inyección de comandos. Nos enfocamos en herramientas de sistemas de control de versiones (VCS), como git y hg (mercurial), que, entre algunas de sus opciones, permiten ejecutar comandos arbitrarios (en determinadas circunstancias).
Los objetivos de esta investigación son aplicaciones web y proyectos de bibliotecas (escritos en cualquier lenguaje de programación) que llaman a estos comandos mediante una API segura. No nos enfocamos en proyectos de CLI ni en proyectos que llaman a esos comandos con datos provenientes de fuentes almacenadas, como archivos de configuración.
En esta publicación, buscamos mostrar escenarios comunes en los que es posible ejecutar comandos arbitrarios mediante opciones de argumentos inyectadas, incluso al usar una API segura. También compartiremos ejemplos de mitigación y sugerencias para quienes mantienen proyectos de código abierto.
Antecedentes sobre la inyección de argumentos
Considera el siguiente escenario: un desarrollador quiere llamar a comandos de git o hg desde el directorio de su proyecto para interactuar con repositorios. Sin embargo, se usan valores controlados por el usuario como parámetros de estos comandos (lo que permite que los usuarios especifiquen la URL del repositorio, el nombre de la rama, etc.). Como el desarrollador sabe que existe la vulnerabilidad de inyección de comandos, decide usar una API segura para ejecutar comandos de shell. Por ejemplo, si el proyecto está en Node.js, la API spawn del módulo child_process previene la inyección de comandos cuando se llama de la siguiente manera:
Sin embargo, no siempre es así. Como veremos pronto, también depende del comando que llamen estas API. Si quieres saber más sobre qué es la inyección de argumentos, este artículo de Staaldraad es un excelente recurso.
¿Por qué debería importarnos git?
Algunos subcomandos de git tienen opciones que permiten al usuario especificar qué programas o comandos se ejecutarán. Un ejemplo es la opción --upload-pack, disponible en los siguientes subcomandos de git:
Tomemos como ejemplo los subcomandos git fetch, ls-remote y pull, que implementan la opción --upload-pack.
Según el comando, hay ciertos argumentos obligatorios seguidos de otros opcionales. Por ejemplo, git fetch requiere al menos un argumento, es decir, un repositorio, seguido de refspec y un conjunto de opciones.
Al ejecutar los siguientes comandos en una terminal, se ejecutará el comando de shell touch HELLO:
¿Cómo se ve el código vulnerable con “git”?
Para mostrar esta vulnerabilidad, usaré JavaScript y Python. Para que los ejemplos sean breves, supongamos que los valores url y branch están controlados por el usuario y no se sanitizan correctamente.
El siguiente ejemplo muestra que aún es posible ejecutar código arbitrario, incluso al usar una API segura que protege contra las inyecciones de comandos clásicas:
JavaScript (NodeJS)
La última línea muestra que una carga útil de inyección de comandos normal (es decir, una carga útil con caracteres especiales como ; & | , etc. para encadenar comandos) no funciona.
Python
Es importante tener en cuenta que los escenarios anteriores pueden llevar a la ejecución de comandos si se proporcionan los argumentos obligatorios para los subcomandos de git correspondientes. Por ejemplo, si solo se proporciona un argumento al subcomando git ls-remote, sin otros argumentos, los escenarios anteriores no funcionarán:
JavaScript (NodeJS)
Esto se debe a que el único argumento proporcionado se interpretará como un repositorio (en el caso de git ls-remote). Si se proporciona otro argumento válido al comando, estaremos en el caso anterior y aún podremos ejecutar código.
Sugerencias de mitigación
Una posible solución para los escenarios anteriores sería agregar -- antes de los valores controlados por el usuario. Como explica la documentación oficial, “al escribir un script que debe manejar entradas aleatorias de usuarios, es una buena práctica aclarar qué argumentos son cuáles colocando -- en los lugares adecuados para evitar ambigüedades”.
Por ejemplo, el siguiente código ya no permitirá ejecutar comandos:
¿Por qué debería importarnos hg (mercurial)?
Como vimos en la sección anterior, deben cumplirse ciertas condiciones para ejecutar comandos arbitrarios (es decir, proporcionar los argumentos obligatorios). Sin embargo, esto no ocurre con hg, lo que hace que su explotación sea mucho más sencilla.
Entre las diversas opciones disponibles, mercurial permite que los usuarios especifiquen aliases y hooks para cada comando (clone, init, log, etc.) mediante la opción –config.
Como explica la documentación de mercurial, los aliases “te permiten definir tus propios comandos a partir de otros comandos (o aliases), con argumentos opcionales. Un alias puede comenzar con un signo de exclamación ("!") para convertirlo en un alias de shell. El alias de shell se ejecuta con el shell y te permite ejecutar comandos arbitrarios. Por ejemplo:
te permitirá ejecutar "hg echo foo" para que se imprima "foo" en tu terminal”.
Por otro lado, los hooks son “comandos o funciones de Python que se ejecutan automáticamente ante diversas acciones, como iniciar o finalizar un commit”. Son comandos que se ejecutan cuando se cumplen determinadas condiciones y pueden ejecutarse antes (pre-<command>) o después (post-<command>) de otros comandos de mercurial.
¿Cómo se ve el código vulnerable al usar “hg init”?
Veamos un ejemplo de cómo es posible ejecutar comandos arbitrarios cuando se llama al comando hg init con entradas del usuario sin sanitizar.
Ejemplo de alias
Se ejecutará el comando touch HELLO. Esto se debe a que se define un alias para el comando init, que se ejecuta en lugar del comando hg init.
El mismo comando ejecutado desde el código se vería así (como antes, supongamos que el usuario controla source y que no se sanitiza correctamente):
JavaScript (NodeJS)
Python
Ejemplo de hooks
El comando touch HELLO se ejecutará antes de ejecutar el comando init (es decir, antes, mediante las opciones pre-<command>).
El mismo comando ejecutado desde el código se vería así:
JavaScript (NodeJS)
Python
A diferencia de git, para ejecutar comandos arbitrarios solo necesitamos controlar un argumento proporcionado a los comandos de mercurial, incluso si es el único parámetro que se usa.
Sugerencias de mitigación
Una vez más, podemos resolver este problema agregando los caracteres -- antes de los argumentos controlados por el usuario.
Por ejemplo, la siguiente explotación ya no funcionará: creará una carpeta de repositorio cuyo nombre será igual al contenido de la variable source.
Resultados
En el pasado ya se explotaron problemas similares. Algunos ejemplos conocidos son los ataques de ejecución remota de código en CocoaPods Trunk y Phabricator.
La siguiente tabla resume los problemas que encontramos y divulgamos como parte de esta investigación:
Proyecto | Vulnerabilidad | Aviso/Referencia | CVE |
|---|---|---|---|
ungit | RCE | CVE-2022-25766 | |
mozilla/pontoon | RCE | N/A | |
GoCD Server | RCE | CVE-2022-29184 | |
Weblate | RCE (x2) | CVE-2022-23915 | |
simple-git | Inyección de comandos | CVE-2022-24433 | |
ruby-git | Inyección de comandos | CVE-2022-25648 | |
cocoapods-downloader | Inyección de comandos | CVE-2022-24440 | |
cocoapods-downloader | Inyección de comandos | CVE-2022-21223 | |
Masterminds/vcs | Inyección de comandos | CVE-2022-21235 | |
git-php | Inyección de comandos | CVE-2022-25866 | |
libvcs | Inyección de comandos | CVE-2022-21187 | |
workspace-tools | Inyección de comandos | CVE-2022-25865 | |
cookiecutter | Inyección de comandos | CVE-2022-24065 | |
hashicorp/go-getter | Inyección de comandos | CVE-2022-26945 |
Después de encontrar estas vulnerabilidades, las reportamos de forma privada a quienes mantienen los proyectos, de acuerdo con nuestra política de divulgación de vulnerabilidades. Quiero agradecer personalmente a todas las personas que contacté por su tiempo y colaboración.
En los siguientes casos de estudio, explicaremos cómo fue posible ejecutar código de forma remota e inyectar comandos en estos proyectos populares.
Caso de estudio: ejecución remota de código en Weblate
Weblate es una “herramienta de localización basada en la web con una integración estrecha con el control de versiones”. Un usuario autenticado podía lograr la ejecución remota de código mediante la inyección de argumentos.
Al agregar nuevos componentes de traducción y usar repositorios de mercurial, la entrada del usuario branch se usa en el comando hg pull sin sanitizarla correctamente.
El código pertinente que llama al comando hg pull con valores controlados por el usuario es:
Prueba de concepto
Para ejecutar comandos arbitrarios, el usuario debe crear un nuevo componente de traducción en un proyecto de traducción y asignar a Rama del repositorio el valor --config=alias.pull=!id>/app/cache/static/output_rce1.txt. En este caso, se ejecutará el comando id y su salida se redirigirá a una página disponible en http://localhost:8888/static/output_rce1.txt.
1. Inicia sesión en la aplicación.
2. Agrega un nuevo proyecto de traducción y haz clic en Guardar.

3. Agrega un nuevo componente de traducción (al proyecto de traducción que acabas de crear), ingresa la carga útil --config=alias.pull=!id>/app/cache/static/output_rce1.txt en el campo Rama del repositorio y haz clic en Continuar.

4. La salida del comando id estará disponible en http://localhost:8888/static/output_rce1.txt.

Mitigación
Como puedes ver, fue posible lograr una ejecución remota de código incluso al trabajar con repositorios de git. En este caso, la entrada del usuario repo, sin sanitizar, se pasó al comando git ls-remote.
Quien mantiene el proyecto corrigió rápidamente ambos problemas en la versión 4.11.1 (consulta Github Release). Las correcciones (para los casos de mercurial y git) agregaron los caracteres -- antes de la entrada del usuario para evitar que se interpretara como un argumento.
Caso de estudio: inyección de comandos en ruby-git
Ruby git es una “biblioteca que se puede usar para crear, leer y manipular repositorios de Git mediante el uso de llamadas al sistema que envuelven el binario de git”.
Al llamar a la función fetch(remote = 'origin', opts = {}), el parámetro remote se pasa al subcomando git fetch sin sanitizarlo. Como vimos antes, git fetch es uno de los subcomandos de git que acepta el argumento --upload-pack. Para obtener más información sobre --upload-pack, consulta la documentación de git-fetch.
El código pertinente que llama al comando git fetch con valores controlados por el usuario es:
Prueba de concepto
La siguiente prueba de concepto muestra cómo fue posible ejecutar comandos arbitrarios:
Mitigación
Los encargados del mantenimiento corrigieron el problema en la versión 1.11.0 (versión de GitHub). Al igual que en el problema anterior, la corrección agregó los caracteres -- antes de los valores controlados por el usuario.
Conclusiones clave
… para desarrolladores
Al llamar comandos mediante una API segura —que evita la inyección de comandos y acepta valores controlados por el usuario—, es importante asegurarse de que los valores proporcionados por el usuario no cambien el comportamiento del comando al inyectar o manipular algunas de sus opciones. Para evitar esta confusión, se pueden usar los caracteres -- para separar los argumentos de los valores controlados por el usuario.
… para encargados del mantenimiento
Si una función o API envuelve llamadas a comandos que aceptan valores controlados por el usuario, podría ser útil documentar este comportamiento para evitar confundir a quienes ven que el encargado del mantenimiento aborda algunos problemas de sanitización (como prevenir la inyección de comandos mediante una API segura), pero no otros (como cuando la entrada puede usarse como una opción peligrosa que provoque un comportamiento inesperado).
… para investigadores de seguridad
Nos enfocamos en los comandos git y hg, pero hay muchos otros que funcionan de manera similar. Cabe mencionar que incluso el uso de API seguras que evitan la inyección de comandos puede dar lugar a problemas de seguridad. Todo depende de los comandos que se ejecuten. Si encuentras problemas similares (o cualquier otra vulnerabilidad de seguridad) en un proyecto de código abierto que participe en nuestro programa, repórtalo mediante el formulario de divulgación de vulnerabilidades de Snyk.



