1 puntos por GN⁺ 6 시간 전 | 1 comentarios | Compartir por WhatsApp
  • En Git, -- no es un terminador general de opciones, sino que separa revisiones y especificaciones de ruta, así que para pasar de forma segura una revisión no confiable se necesita --end-of-options, compatible desde Git 2.24.0
  • En git log --end-of-options "$rev" -- "$path", el marcador de adelante separa opciones y revisiones, y el -- de atrás separa revisiones y rutas; no se pueden sustituir entre sí
  • Aunque se ejecute directamente el arreglo argv sin shell, si una entrada que empieza con guion se interpreta como una opción como --upload-pack, core.sshCommand o ProxyCommand, se produce inyección de argumentos CWE-88
  • De 19 gestores de paquetes analizados, 17 ejecutaban el binario de Git como método predeterminado o único, pero la única herramienta que usaba --end-of-options era cmd/go de Go
  • La mitigación de fondo tiene un costo de compatibilidad: según el subcomando, hay que subir la versión mínima de Git a 2.24.0, 2.30.0 o 2.43.1; las bibliotecas de Git eliminan el límite de inyección de argumentos, pero a cambio hay que seguir directamente los parches upstream de seguridad en checkout

Diferencia entre -- y --end-of-options

  • En las herramientas Unix comunes, -- marca el fin del análisis de opciones, así que rm -- -f trata -f como nombre de archivo y no como opción de borrado forzado
  • Git usó desde temprano -- como separador entre revisiones y especificaciones de ruta (pathspec)
    • git log foo es ambiguo: puede referirse a una rama llamada foo o a un archivo
    • git log main -- README.md significa los commits de main que tocaron README.md
  • Por este diseño, no había un marcador de fin de opciones en la posición de revisión, y en git log "$rev", si $rev empieza con guion Git lo interpreta como una opción
  • El commit que introdujo --end-of-options explica que, como -- ya separaba revisiones y especificaciones de ruta, hacía falta un marcador aparte para distinguir opciones y revisiones
  • --end-of-options está documentado en gitcli(7) y se agregó en noviembre de 2019, cuando salió Git 2.24.0

Uso correcto según el comando

  • git clone -- "$url" hace que clone termine de interpretar opciones antes de la URL, porque sigue la convención POSIX
  • El -- final de git checkout "$ref" -- marca $ref como revisión y no como nombre de archivo, pero no evita que $ref se interprete primero como opción
  • Para pasar de forma segura una revisión no confiable y una ruta al mismo tiempo, hay que usar ambos marcadores, como en git log --end-of-options "$rev" -- "$path"
    • --end-of-options separa opciones y revisiones
    • -- separa revisiones y rutas
  • Si se tratan ambos marcadores como intercambiables, no se bloquean las entradas que empiezan con guion

Momento de soporte distinto según el subcomando

  • El soporte de --end-of-options no llegó a todos los comandos de Git al mismo tiempo, sino que se fue agregando por subcomando
  • git rev-parse usa su propio parser de argumentos y empezó a soportarlo recién un año después de la introducción inicial, en Git 2.30.0
  • git checkout y git reset interpretan -- por su cuenta, y la implementación inicial rechazaba --end-of-options porque lo dejaba en la lista de argumentos
    • Este problema se resolvió en febrero de 2024, cuando salió Git 2.43.1

Inyección de argumentos incluso sin shell

  • Git, Mercurial y SSH tienen como función oficial opciones que ejecutan comandos indicados por quien llama
    • git clone --upload-pack=<cmd> especifica el binario del lado del servidor
    • -c core.sshCommand=<cmd> en cualquier llamada a Git cambia el comando de conexión
    • --config=alias.<subcmd>=!<shell> en Mercurial redefine un subcomando a ejecutar como script de shell arbitrario
    • -oProxyCommand=<cmd> en SSH especifica el comando proxy
  • Si un programa wrapper mete una cadena no confiable en la lista de argumentos, estas funciones pueden convertirse en vector de ataque
  • Este tipo de fallo corresponde a CWE-88, inyección de argumentos (argument injection), y es distinto de la inyección de comandos de shell
    • Puede ocurrir aunque el programa use argv y exec en vez de system()
    • El arreglo llega intacto a Git, pero Git interpreta como opciones los argumentos que empiezan con guion
  • El CVE-2019-13139 de docker build fue un caso que usaba os/exec de Go y un arreglo argv, sin pasar por shell
    • El fragmento #ref:dir de una URL de contexto Git se pasaba a git fetch origin <ref>, donde <ref> se interpretaba como --upload-pack=<cmd>

Vulnerabilidades repetidas en varios sistemas de control de versiones

  • El mismo día de agosto de 2017 se publicó el mismo patrón en cuatro sistemas de control de versiones
  • Los cuatro sistemas pasaban el hostname de la URL como argumento a SSH, y un hostname que empezara con -oProxyCommand= se trataba como opción de SSH
  • Según el análisis posterior de Phabricator, de las tres herramientas activamente mantenidas en ese momento, solo Subversion agregaba -- antes del hostname
    • Git y Mercurial validaban el formato del hostname también porque no todas las implementaciones de SSH soportan --
  • Incluso el código al que le falta -- parece normal y funciona hasta que aparece un argumento que empieza con guion, así que este mecanismo es inseguro por diseño

Ruta de exposición en gestores de paquetes

  • Los gestores de paquetes reciben URLs o refs de Git desde manifests, lockfiles y metadatos de dependencias transitivas, y los pasan a subprocesos
    • gem 'foo', git: '...' en Gemfile
    • github:user/repo#ref en package.json
    • Configuraciones equivalentes en pyproject.toml, Cargo.toml, mix.exs, Package.swift, pubspec.yaml, conanfile.py y go.mod
  • A julio de 2026, tomando HEAD como referencia, 17 de los 19 gestores de paquetes analizados ejecutaban el binario de Git como ruta predeterminada o única
  • Los otros dos usan bibliotecas por defecto
    • Cargo usa libgit2 y ejecuta el proceso de Git si se activa net.git-fetch-with-cli
    • Poetry cambió a dulwich desde 1.2.0 y puede usar Git del sistema con la configuración system-git-client
  • Nix usa libgit2 para leer repositorios locales, pero ejecuta el proceso de Git para fetch porque libgit2 no soporta el helper git-credential
  • Se analizaron Bundler, Cargo, CocoaPods, Composer, Conan, Go, Helm, Homebrew, Mix, Nix, npm, pip, pnpm, Poetry, Pub, SwiftPM, uv, vcpkg y Yarn

CVE confirmados en gestores de paquetes

Estado real de las defensas y corrección en Go

  • De los 17 gestores de paquetes que ejecutan el proceso de Git, la única herramienta que usaba --end-of-options era cmd/go de Go
  • En junio de 2019, Go agregó -- antes de la URL del repositorio como endurecimiento defensivo general
  • En enero de 2026 se vio que -- solo no bastaba, y la corrección de CVE-2025-68119 agregó --end-of-options de forma amplia
  • La misma corrección incluyó HGPLAIN=+strictflags
    • Esa configuración restringe el análisis inicial de opciones en Mercurial desde que salió Mercurial 4.4.2 en 2017
  • El commit de corrección en Go indica que quizá haga falta un cambio más estructural para evitar que el mismo problema vuelva a introducirse, pero por ahora resuelve el problema actual

La mayoría de las defensas se agregaron después de divulgar la vulnerabilidad

  • Los demás gestores de paquetes, incluso cuando protegen la lista de argumentos, usan sobre todo -- o el rechazo de guion inicial en la entrada
  • El -- antes de la URL de git clone en Bundler se agregó con el parche de CVE-2021-43809
  • El rechazo de guion inicial en cocoapods-downloader se aplicó en tres commits durante diez días en marzo de 2022, coincidiendo con la divulgación de CVE-2022-21223
  • La defensa de Poetry se agregó en septiembre de 2021, recibió un CVE un año después y, seis meses más tarde, cambió a dulwich
  • vcpkg es una excepción: usó -- desde el primer día en que se escribió el soporte para registros Git

Restricciones de compatibilidad impuestas por la versión mínima de Git

  • El aviso de CVE-2022-24828 de Composer señala --end-of-options como la corrección correcta, pero como también debe soportar versiones viejas de Git, eligió rechazar nombres de rama que empiecen con guion
  • La integración de Git en vcpkg fija como versión mínima Git 2.7.4, y HOMEBREW_MINIMUM_GIT_VERSION para Linux en Homebrew está fijado en 2.7.0 desde 2018
  • Amazon Linux 2, que traía Git 2.14.3, terminó su vida útil en junio de 2026, y recién ahora las distribuciones que seguían ese piso están saliendo del rango de soporte
  • El estado de soporte extendido de Ubuntu también dificulta una transición uniforme
    • Ubuntu 18.04 trae Git 2.17.0 y tiene soporte extendido hasta 2028
    • Ubuntu 20.04 trae Git 2.25.1 y tiene soporte extendido hasta 2030
    • Git 2.25.1 acepta --end-of-options en git fetch, pero lo rechaza en git rev-parse
  • Para depender de --end-of-options, la mayoría de los subcomandos requiere como mínimo Git 2.24.0; rev-parse, 2.30.0; y checkout y reset, 2.43.1
  • Subir la versión mínima deja sin soporte a usuarios que siguen usando Git antiguo incluido en su distribución

Usar bibliotecas de Git en vez de ejecutar procesos

  • libgit2, gitoxide, go-git, JGit y dulwich implementan el protocolo de transporte de Git necesario para clone y fetch dentro del mismo proceso
  • Como no existe un límite argv separado, no hay un objetivo donde inyectar argumentos
  • Jujutsu usa gitoxide para integrarse con Git y no tiene CVE públicos de este tipo de inyección de argumentos
    • Sus dos avisos actuales son por path traversal y por la falta de verificación de colisiones SHA-1 heredada de la biblioteca
  • El CVE-2025-21613 de go-git está limitado al transporte file://
    • Esa ruta es el único camino en go-git que ejecuta el binario de Git
  • Incluir una implementación propia de Git obliga a seguir todos los parches de seguridad en checkout que publique Git upstream, y tanto libgit2 como JGit ya han tenido correcciones repetidas relacionadas con eso
  • Ese costo es real, pero cambia el problema: en vez de recordar para siempre validar argumentos en cada punto de llamada, pasa a ser aplicar un flujo concreto de parches upstream

Alcance del cambio propuesto para Homebrew

  • El PR de Homebrew eleva la versión mínima de Git a 2.30.0 y agrega --end-of-options en estos lugares
    • Antes de la URL en clone, remote set-url y ls-remote
    • Antes del ref en rev-parse
  • No cambia las llamadas a checkout ni reset
    • Para proteger también esos dos comandos hace falta Git 2.43.1, lanzado en febrero de 2024
    • Esa versión es más nueva que la de Git incluida en varias distribuciones actualmente soportadas

1 comentarios

 
GN⁺ 6 시간 전
Opiniones en Lobste.rs
  • Últimamente me estoy enganchando cada vez más con jj. Comparado con este desorden de Git, hasta se siente tranquilizador; todavía lo estoy aprendiendo, pero refleja bien la intención y también permite entender fácilmente la forma de trabajar que quiero
    En particular, lo aprendido en un comando de jj se aplica de forma natural a otros comandos. La documentación de git log tiene una explosión de flags según las opciones de salida, mezcladas además con la “notación especial” de la sintaxis de rangos de commits y flags de filtrado adicionales, y hay poco conocimiento que se transfiera a otros comandos de Git
    En cambio, la documentación de jj log es tan concisa que sobra espacio incluso en una sola página. Reemplazó la complejidad tipo cajón de sastre de Git por tres cosas: conjuntos de revisiones, conjuntos de archivos y una DSL de plantillas de salida; como se usan de forma consistente en todo jj, resulta mucho más simple, componible e intuitivo. Hay que tener en cuenta que Git acumuló ruido durante 20 años, pero jj parece estar en una posición mucho mejor para evitarlo
  • Sé que en los comandos normalmente se necesita -- antes de los argumentos proporcionados por el usuario, pero si además se exigen reglas de transformación propias, se convierte en una trampa demasiado peligrosa
  • Es el resultado honesto de haber vuelto la herramienta excesivamente compleja. Git parece el VASAXA de nuevo cuño
  • Me da curiosidad por qué originalmente se eligió -- para resolver la ambigüedad de los argumentos de archivo. Me pregunto si hubo algún compromiso de diseño no evidente para elegir esto en lugar de una forma simple de recibir archivos como argumentos explícitos
    • Usar la ya establecida convención de parseo de opciones de UNIX para un propósito completamente distinto fue, como era de esperar, una tontería, y una decisión muy al estilo de Git
  • La filosofía de UNIX de que todo es texto vuelve a causar problemas. La línea de comandos es un ejemplo perfecto de datos estructurados y, aun así, parece que el costo de coordinación colectiva para salir de este pozo es demasiado alto
    Si miramos atrás al debate entre Tcl y Scheme en los años 90, quizá en este caso “lo peor es mejor” sí era cierto. Mientras que la familia de las expresiones S, incluidos JSON y XML, casi no logró arraigarse en el ecosistema UNIX, Tcl tenía un enfoque relativamente principista de estratificar sublenguajes dentro de cadenas