2 puntos por GN⁺ 2024-05-26 | 1 comentarios | Compartir por WhatsApp
  • La base de datos de checksums de Go y el proxy público de módulos pueden aceptar incluso repositorios que no contienen código Go, lo que crea una vía para cargar datos arbitrarios de repositorios Git en la infraestructura de Go y volver a descargarlos
  • Cuando una solicitud a sum.golang.org/lookup/$module@$version encuentra una versión de módulo que aún no está registrada, la obtiene del servidor de origen; en este proceso, el zip de ese repositorio también queda disponible en proxy.golang.org
  • El repositorio Ruby de Homebrew y un fork de un repositorio Rust estaban en la base de datos de checksums, y los experimentos lograron registrar como pseudo-version tanto un módulo Go nuevo como un repositorio sin archivos Go
  • Los zips de módulos tienen un límite máximo de 500 MiB, tanto comprimidos como sin comprimir, pero es un tamaño suficientemente grande para evadir restricciones de descarga en máquinas de desarrolladores y CI/CD, almacenar payloads e implementar C2
  • De unas 1.59 millones de rutas únicas en sum.golang.org, unas 1.51 millones eran rutas de GitHub, cerca del 95%, lo que expone tanto la dependencia del ecosistema Go respecto de GitHub como la posibilidad de abusar del proxy público

Repositorios no-Go encontrados en la base de datos de checksums de Go

  • Mientras se examinaba la base de datos de checksums de Go, github.com/homebrew/homebrew-core aparecía con mucha frecuencia en la tabla modules
    • github.com/homebrew/homebrew-core: 39,438
    • github.com/Homebrew/homebrew-core: 30,896
    • github.com/concourse/concourse: 25,372
    • github.com/openshift/release: 24,065
    • github.com/cilium/cilium: 22,138
  • El repositorio de Homebrew es conocido por usar Ruby, y no se encontraron go.mod ni archivos fuente Go ni en el repositorio ni en los archivos clonados
  • La diferencia entre mayúsculas y minúsculas se explica por la regla de case encoding de la documentación de Go
    • Las mayúsculas se codifican con ! y la minúscula correspondiente, lo que permite almacenar juntos example.com/M y example.com/m incluso en sistemas de archivos que no distinguen mayúsculas de minúsculas
  • github.com/Edu4rdSHL/rust-headless-chrome también es un fork de un repositorio Rust no relacionado con Go, pero aparece en la base de datos de checksums

Cómo /lookup obtiene repositorios

  • Según la Go Modules Reference de la documentación de módulos de Go, cuando el comando Go consulta la base de datos de checksums, primero obtiene los datos del registro desde el endpoint /lookup
  • Si la versión del módulo aún no está registrada en el log, la base de datos de checksums intenta obtener ese módulo desde el servidor de origen antes de responder
  • El formato del endpoint es $base/lookup/$module@$version
    • Devuelve el número de registro en el log para $version de $module, las líneas de go.sum y la descripción firmada del árbol
  • Al consultar una pseudo-version de github.com/homebrew/homebrew-core, se devuelven un registro de checksum y el hash de go.mod
  • Si el repositorio no tiene tags de versión, se usan las reglas de pseudo-version de Go

Experimento de registro de un módulo Go nuevo

  • Después de crear un nuevo módulo Go, github.com/gdbinit/fluxmatter, se verificó su registro mediante una solicitud lookup
  • La consulta @latest devolvió un error indicando que no era una versión canónica
    • bad request: version "latest" is not canonical
  • La consulta @v0.0.0 devolvió un error indicando que no conocía esa revision
    • not found: ... invalid version: unknown revision v0.0.0
  • Sin embargo, al volver a sincronizar y consultar la base de datos de checksums, el módulo ya estaba registrado
    • github.com/gdbinit/fluxmatter|v0.0.0-20240524163826-a7e64ffd69f2|2024-05-24T16:40:51.203837Z
  • proxy.golang.org/github.com/gdbinit/fluxmatter/@latest devolvió la pseudo-version e información del origen en GitHub, y también fue posible descargar y verificar la compresión del archivo zip de esa versión
  • Para la siembra inicial no hace falta especificar una versión exacta: basta con una consulta lookup que contenga la ruta del módulo y un valor que parezca una versión

También se cargan repositorios sin código Go en el proxy público

  • El mismo experimento funcionó con el repositorio github.com/gdbinit/readmem, que no contiene nada de código Go
  • La solicitud lookup devolvió un error indicando que no conocía la revision v0.0.0, pero el repositorio quedó registrado en la base de datos de checksums como pseudo-version
    • github.com/gdbinit/readmem|v0.0.0-20131006075740-407cb0a56933|2024-05-24T16:45:35.88456Z
  • El @latest de proxy.golang.org devolvió la pseudo-version de ese repositorio y la información del origen Git
  • El zip descargado contenía Entitlements.plist, README, archivos de proyecto de Xcode, main.c, etc., no archivos Go
  • Este experimento usó un repositorio de GitHub, pero podría ser posible en otros sitios de hosting si el VCS funciona

Dependencia de GitHub y límites de tamaño

  • La cantidad de rutas únicas en la base de datos de checksums era de 1,591,375, y de ellas 1,515,957 eran rutas github.com%
  • Aproximadamente el 95% de las rutas únicas están alojadas en GitHub {p:95}
  • Esta cifra es una estadística cruda que no elimina forks ni objetivos que en realidad no sean código Go
  • A los zips de módulos Go se les aplican restricciones de ruta y tamaño de archivos
    • El archivo zip del módulo puede tener como máximo 500 MiB
    • El tamaño total sin comprimir de los archivos también puede ser de máximo 500 MiB
    • El archivo go.mod puede tener como máximo 16 MiB
    • El archivo LICENSE también puede tener como máximo 16 MiB
  • Estas restricciones son un mecanismo para mitigar ataques de denegación de servicio contra usuarios, proxies y otras partes del ecosistema de módulos
  • 500 MiB es un tamaño suficientemente grande si se considera su potencial de abuso

Posibles escenarios de abuso

  • El proxy público de Go puede usarse para evadir restricciones de descarga por destino en máquinas de desarrolladores o servidores CI/CD
    • Se asume una situación sin un GOPROXY privado
    • El malware puede subir un payload a un repositorio y descargarlo desde el proxy cuando lo necesite
    • Incluso si desaparece la fuente original, en la entrada de la base de datos de checksums podría quedar solo un rastro pequeño
  • Un DoS contra proxy.golang.org podría ser difícil de ejecutar
    • Se puede pedir al proxy que descargue repositorios Git arbitrarios
    • Un posible ataque consistiría en recolectar muchas URL de GitHub y enviar muchas solicitudes a la API lookup
    • No se conoce la implementación del servidor, pero podría haber límites de paralelismo, como una work queue
    • También es posible que actúen las protecciones de ancho de banda del lado de GitHub
    • También existe la posibilidad de un DoS contra el almacenamiento, pero es solo una conjetura
  • Es fácil construir C2(command and control) encima de proxy.golang.org
    • Con una consulta @latest se puede encontrar la versión más reciente de un módulo específico
    • El payload puede ser un archivo simple, o puede ocultarse dentro de go.mod o de archivos fuente Go
    • Para evitar usar un solo repositorio, se puede usar un module DGA

Flujo de descarga de C2

Conclusión y preguntas abiertas

  • La base de datos de checksums y el proxy de Go pueden, siguiendo procedimientos documentados, obtener desde el servidor de origen y almacenar módulos que aún no estén registrados
  • El estado actual no parece ser un problema grave de la infraestructura de Go, pero puede abusarse de él con facilidad y hay margen de mejora
  • Puede haber una razón documentada o privada por la que se permite que repositorios que no son código Go se suban al proxy y a la base de datos de checksums
  • Para verificar si alguien ya está abusando de esto, habría que investigar cerca de 1.6 millones de repositorios únicos y alrededor de 22 millones de entradas según la base de datos local más reciente
  • Por qué algunos proyectos non-Go válidos están en la base de datos sigue siendo una pregunta abierta

1 comentarios

 
GN⁺ 2024-05-26
Opiniones en Hacker News
  • Cualquier servicio en línea donde los usuarios suben material y ese material aparece públicamente termina usándose para comando y control, infracción de derechos de autor y hosting de CSAM.
    Esto ocurre especialmente con servicios que, además de alojar archivos, tienen usos importantes y por eso son difíciles de bloquear; ya pasó con Twitter[1], Telegram[2] e infraestructura de claves PGP[3], por no hablar de objetivos obvios como GitHub.
    [1] https://pentestlab.blog/2017/09/26/command-and-control-twitt...
    [2] https://www.blazeinfosec.com/post/leveraging-telegram-as-a-c...
    [3] https://torrentfreak.com/openpgp-keyservers-now-store-irremo...

    • Gmail, Google Groups, Google Drive y Gchat también fueron lo mismo, y los datos almacenados ni siquiera tenían que ser públicos.
      En el caso de Gmail, distribuían credenciales para que iniciaran sesión y luego leyeran, vía IMAP, los archivos adjuntos subidos.
      Fui SAD-SRE (Spam, Abuse, Delivery) en Google.
    • PyPI también parece fácil de usar con este fin, porque permite poner archivos arbitrarios que no sean Python dentro de un paquete.
      También se puede meter un archivo codificado en Base64 dentro de una cadena de código Python.
    • No sé si ya habrá ocurrido, pero un objetivo menos obvio parece ser HuggingFace.
  • Soy Googler y esta es mi opinión personal; no conozco bien esta área.
    Espero que el equipo de Go haya colaborado con los equipos de GCP y Drive, porque el hosting de archivos maliciosos es un problema que Google maneja constantemente.
    No es muy distinto de otros endpoints donde Google ya permite que la gente suba datos arbitrarios.

    • Soy ex-Googler y no conozco bien al equipo de Go Dev Tools, pero Google es de las grandes empresas, entre aquellas donde trabajé o de las que escuché por amigos cercanos, que mejor hacen este tipo de colaboración interna.
      Google es muy bueno haciendo que equipos centrales administren infraestructura y la compartan en toda la empresa. Salvo cuando se trata de apps de mensajería; y, puramente como conjetura, el equipo de Go probablemente esté usando un almacenamiento interno de blobs, y también debe de haber un equipo interno de infraestructura que automatiza la respuesta ante abusos y el escaneo de archivos.
  • En PyPI también hay bastantes proyectos que no son Python.
    Como puede que los usuarios de Python no puedan compilar el código de una biblioteca, hace falta la capacidad de distribuir wheels, que son binarios compilados.
    Ese tipo de código muchas veces está escrito en C, pero también puede ser Golang[1], y aunque no pude encontrar un ejemplo, creo haber visto que se usaba no para bibliotecas sino también para distribuir aplicaciones.
    Es bastante genial escribir una app en C, subirla a PyPI y decirles a los usuarios que la instalen con pip install.
    [1] https://github.com/popatam/gopy_build_wheel_example

    • Por ejemplo, aunque se agregara un requisito de uso de Python, alguien que quisiera cumplirlo de forma maliciosa probablemente solo tendría que ofrecer un mínimo de código stub en Python.
      Sería como si en Linux ls estuviera escrito en Python; creo que es mejor no jugar a ese juego.
    • Este uso habría sido mucho más útil antes de que pip exigiera estar dentro de un entorno como venv/virtualenv/pipenv/pyenv para descargar paquetes.
    • También existe pip install cmake, y los binarios propietarios son posibles como pip install nvidia-cudnn-cu12.
    • Últimamente estoy usando mucho PyPI para herramientas que no son Python como FFmpeg y Eigen.
      Es una de las razones por las que pude abandonar por completo Homebrew.
  • Quizá sea una idea ingenua, pero no entiendo en qué se diferencia esto de subir archivos a un repositorio de GitHub.
    ¿La única diferencia es que en GitHub hay que crear una cuenta? En GitHub también se pueden almacenar datos arbitrarios, y tampoco hay un límite de 500 MB.

    • GitHub tiene límites de solicitudes bastante estrictos para solicitudes anónimas.
  • El sistema de módulos de CUE por fin está saliendo, y aunque MVS es parecido al de Go, está construido sobre infraestructura OCI.
    Si te interesan los sistemas de gestión de dependencias, estos enlaces pueden servirte.
    proposal: https://github.com/cue-lang/proposal/tree/main/designs/modul...
    custom registry: https://cuelang.org/docs/tutorial/working-with-a-custom-modu...
    road map: https://github.com/orgs/cue-lang/projects/10/views/8
    Desde 0.9.0-alpha-5, los módulos vienen activados por defecto: https://github.com/cue-lang/cue/releases/tag/v0.9.0-alpha.5
    En Go Sum, el proyecto Trillian respalda el log de transparencia: https://github.com/google/trillian
    CUE planea apoyarse en opciones de OCI como las attestations.

    • No entiendo qué tiene que ver eso con el artículo enlazado.
  • Estuve jugando con la idea de subirme al proxy de golang y a sumdb —es decir, abusar de ellos— para crear un registro de transparencia gratuito de checksums de URLs arbitrarias
    https://getsum.pub/

  • Quizá sea yo el tonto, pero no entiendo exactamente cuál es el problema aquí
    Que el proxy cachee repositorios que no son de Go puede ser un poco desperdicio, pero aunque no hiciera eso, ¿no podrías hacer que cachee repositorios de Go y de todos modos lograr que almacene datos arbitrarios?
    Si no me estoy perdiendo de algo, suena como algo totalmente sin importancia

    • Creo que no te estás perdiendo de nada
      Lo novedoso aquí parece ser apenas que un proxy público sin seguridad acepta cosas para proxyear y las sirve públicamente de forma insegura
      El artículo dice que algunas redes monitoreadas podrían confiar más en una URL del proxy de golang que en una URL web arbitraria, por lo que podría usarse para evadir filtros de reputación y cosas así, pero ya hay varias formas de hacerlo y esta no parece especialmente notable
  • Saliéndome del tema, al ver el dominio me dio mala espina el juego de palabras, así que revisé put.as, y era más o menos lo que esperaba

    • https://put.as/ es algo NSFW
    • Cometí el error de abrirlo en el trabajo
    • Es una palabra en plural en español. Pero…
  • Es un issue ya conocido: https://github.com/golang/go/issues/31866

    • Esa corrección ayudaría a evitar errores accidentales, pero quien quiera hacerlo a propósito solo tendría que agregar archivos .mod y .go en la raíz, ¿no?
    • No me sorprende en absoluto que Marwan esté en este issue
      Él y Aaron crearon Athens y, hasta donde sé, Marwan escribió la primera implementación del protocolo de descarga de Go en la que se basó Athens
      Lo interesante de este issue es que Athens ya usa el comando go mod download -json, mencionado como verificación previa de módulos
      En general, si el repositorio pasa como un módulo que los comandos de módulos de Go entienden, Athens lo sirve
      Dicho en términos más prohibidos, tienes que poder crear versiones de módulo, pseudoversiones y +incompatible, y ese módulo y sus dependencias tienen que generar checksums válidos
      Los checksums de módulos actualmente tienen que ver con incluir el .mod y todos los archivos, así como cada dependencia de forma recursiva
      Así que, como dice el autor, con solo un programa básico en Go puedes tener por diseño mucho espacio para archivos arbitrarios
  • El W3C sentó las bases para que todo en la web pudiera cachearse agresivamente, así que es raro que haya tan pocos cachés proxy de propósito general
    ¿Será que los publicadores están enviando respuestas con Cache-Control: max-age corto o Vary: Cookie aunque no lo necesiten?
    ¿Será que los ISP están pagando demasiado por tránsito en lugar de peering?

    • En general, no hay forma de garantizar que el caché no haya alterado el contenido
      Por ejemplo, en sitios que no usan HTTPS, un proxy del ISP podría insertar anuncios
      Las descargas de software suelen tener firmas y checksums, pero el contenido arbitrario rara vez tiene algo así