2 points par GN⁺ 2024-05-26 | 1 commentaires | Partager sur WhatsApp
  • La checksum database de Go et le proxy public de modules peuvent accepter même des dépôts sans code Go, ce qui ouvre une voie pour charger des données de dépôts Git arbitraires dans l’infrastructure Go puis les retélécharger
  • Une requête sum.golang.org/lookup/$module@$version récupère le module depuis le serveur d’origine lorsqu’elle rencontre une version de module non encore consignée, et dans ce processus le zip du dépôt est aussi mis à disposition sur proxy.golang.org
  • Le dépôt Ruby de Homebrew et un fork Rust figuraient dans la checksum database, et une expérience a confirmé que non seulement de nouveaux modules Go, mais aussi des dépôts sans fichier Go, peuvent être enregistrés avec une pseudo-version
  • Les zips de modules sont limités à 500 MiB maximum en taille compressée comme décompressée, mais cela reste assez grand pour contourner des restrictions de téléchargement sur des postes de développeurs ou des environnements CI/CD, stocker des payloads ou mettre en place un C2
  • Parmi environ 1,59 million de chemins uniques de sum.golang.org, environ 1,51 million sont des chemins GitHub, soit près de 95 %, ce qui met en évidence à la fois la dépendance de l’écosystème Go à GitHub et le potentiel d’abus du proxy public

Dépôts non-Go trouvés dans la checksum database de Go

  • En examinant la checksum database de Go, github.com/homebrew/homebrew-core apparaissait très souvent dans la table 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
  • Le dépôt Homebrew est connu pour utiliser Ruby, et ni le dépôt ni ses fichiers clonés ne contenaient de go.mod ou de fichiers source Go
  • La différence de casse s’explique par la règle de case encoding de la documentation Go
    • Les majuscules sont encodées avec ! suivi de la lettre en minuscule, ce qui permet de stocker à la fois example.com/M et example.com/m sur des systèmes de fichiers insensibles à la casse
  • github.com/Edu4rdSHL/rust-headless-chrome apparaît aussi dans la checksum database alors qu’il s’agit d’un fork d’un dépôt Rust sans lien avec Go

Comment /lookup récupère les dépôts

  • D’après la Go Modules Reference de la documentation Go, la commande Go récupère d’abord les données d’enregistrement depuis l’endpoint /lookup lorsqu’elle interroge la checksum database
  • Si la version du module n’a pas encore été inscrite dans le journal, la checksum database tente de récupérer ce module depuis le serveur d’origine avant de répondre
  • Le format de l’endpoint est $base/lookup/$module@$version
    • Il renvoie le numéro d’enregistrement dans le journal pour la $version de $module, les lignes go.sum et une description d’arbre signée
  • Lorsqu’on interroge une pseudo-version de github.com/homebrew/homebrew-core, l’enregistrement de checksum et le hash de go.mod sont renvoyés
  • Si le dépôt n’a pas de tag de version, la règle des pseudo-versions de Go est utilisée

Expérience d’enregistrement d’un nouveau module Go

  • Après avoir créé le nouveau module Go github.com/gdbinit/fluxmatter, un appel lookup a servi à vérifier s’il était enregistré
  • Une requête sur @latest renvoie une erreur indiquant qu’il ne s’agit pas d’une version canonique
    • bad request: version "latest" is not canonical
  • Une requête sur @v0.0.0 renvoie une erreur indiquant que cette révision est inconnue
    • not found: ... invalid version: unknown revision v0.0.0
  • Mais après avoir resynchronisé la checksum database puis relancé la requête, le module y figurait bien
    • github.com/gdbinit/fluxmatter|v0.0.0-20240524163826-a7e64ffd69f2|2024-05-24T16:40:51.203837Z
  • proxy.golang.org/github.com/gdbinit/fluxmatter/@latest renvoyait la pseudo-version et les informations d’origine GitHub, et le zip de cette version pouvait être téléchargé et validé
  • Pour l’amorçage initial, il n’est pas nécessaire d’indiquer une version exacte : une simple requête lookup contenant un chemin de module et une valeur ressemblant à une version suffit

Même les dépôts sans code Go sont chargés dans le proxy public

  • La même expérience a aussi réussi avec github.com/gdbinit/readmem, un dépôt ne contenant absolument aucun code Go
  • La requête lookup renvoyait une erreur disant que la révision v0.0.0 était inconnue, mais la checksum database l’a tout de même enregistré avec une pseudo-version
    • github.com/gdbinit/readmem|v0.0.0-20131006075740-407cb0a56933|2024-05-24T16:45:35.88456Z
  • Le @latest de proxy.golang.org renvoyait la pseudo-version de ce dépôt et les informations sur son origine Git
  • Le zip téléchargé contenait non pas des fichiers Go mais Entitlements.plist, README, des fichiers de projet Xcode, main.c, etc.
  • L’expérience a été menée avec un dépôt GitHub, mais cela pourrait aussi fonctionner avec d’autres plateformes d’hébergement dès lors que le VCS est pris en charge

Dépendance à GitHub et limites de taille

  • La checksum database comptait 1 591 375 chemins uniques, dont 1 515 957 correspondant à github.com%
  • Environ 95 % des chemins uniques sont hébergés sur GitHub {p:95}
  • Ce chiffre est une statistique brute qui n’exclut ni les forks ni les dépôts qui ne contiennent pas réellement de code Go
  • Les zips de modules Go sont soumis aux File path and size constraints
    • taille maximale du fichier zip de module : 500 MiB
    • taille totale maximale des fichiers une fois décompressés : 500 MiB
    • taille maximale du fichier go.mod : 16 MiB
    • taille maximale du fichier LICENSE : 16 MiB
  • Ces limites visent à atténuer les attaques par déni de service contre les utilisateurs, les proxies et d’autres composants de l’écosystème des modules
  • 500 MiB reste une taille largement suffisante lorsqu’on considère les possibilités d’abus

Scénarios d’abus possibles

  • Le proxy public Go peut servir à contourner des restrictions de téléchargement par destination sur des machines de développeurs ou des serveurs CI/CD
    • en supposant l’absence de GOPROXY privé
    • un malware peut déposer son payload dans un dépôt puis le récupérer via le proxy au moment voulu
    • même si la source d’origine disparaît, l’entrée de la checksum database peut n’en laisser qu’une faible trace
  • Une attaque DoS contre proxy.golang.org peut être difficile à exécuter
    • il est possible de demander au proxy de télécharger des dépôts Git arbitraires
    • une attaque plausible consisterait à collecter massivement des URL GitHub puis à envoyer de nombreuses requêtes à l’API lookup
    • l’implémentation côté serveur n’est pas connue, mais il peut exister des limitations de parallélisme via une file de travaux
    • des protections de bande passante côté GitHub pourraient aussi s’activer
    • un DoS visant l’espace de stockage est également envisageable, mais cela reste spéculatif
  • Il est facile de construire un C2 (command and control) au-dessus de proxy.golang.org
    • une requête @latest permet de trouver la dernière version d’un module donné
    • le payload peut être un simple fichier, ou être caché dans go.mod ou dans des fichiers source Go
    • pour éviter l’usage d’un dépôt unique, on peut utiliser un module DGA

Flux de téléchargement du C2

Conclusion et questions en suspens

  • La checksum database et le proxy Go peuvent, conformément à la procédure documentée, récupérer puis stocker des modules non encore consignés depuis leur serveur d’origine
  • En l’état, cela ne semble pas constituer un problème critique de l’infrastructure Go, mais le mécanisme est facile à détourner et laisse de la place à des améliorations
  • Il existe peut-être une raison documentée ou non publique expliquant pourquoi des dépôts non-Go peuvent être versés dans le proxy et la checksum database
  • Pour vérifier si quelqu’un abuse déjà de ce mécanisme, il faudrait examiner environ 1,6 million de dépôts uniques et près de 22 millions d’entrées dans une base locale récente
  • La présence de certains projets non-Go pourtant valides dans la base reste une question ouverte

1 commentaires

 
GN⁺ 2024-05-26
Avis sur Hacker News
  • Tout service en ligne où les utilisateurs mettent en ligne du contenu qui apparaît publiquement finit par être utilisé pour du command-and-control, des atteintes au droit d’auteur et l’hébergement de contenus pédopornographiques
    C’est particulièrement vrai pour les services difficiles à bloquer parce qu’ils ont d’autres usages importants que l’hébergement de fichiers ; c’est déjà arrivé avec Twitter[1], Telegram[2] et l’infrastructure de clés PGP[3], sans même parler des cibles évidentes comme 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 et Gchat l’ont aussi été, et les données stockées n’avaient même pas besoin d’être publiques
      Dans le cas de Gmail, des identifiants étaient distribués pour permettre de se connecter, puis de lire via IMAP les pièces jointes qui avaient été téléversées
      J’étais auparavant chez Google, dans l’équipe SAD-SRE (Spam, Abuse, Delivery)
    • PyPI semble aussi facile à utiliser à cette fin, puisqu’on peut mettre des fichiers non Python arbitraires dans un paquet
      On peut aussi encoder un fichier en Base64 dans une chaîne de code Python
    • Je ne sais pas si cela a déjà existé, mais une cible moins évidente semble être HuggingFace
  • Je suis Googler, ceci est mon avis personnel, et je ne connais pas bien ce domaine
    J’espère que l’équipe Go a collaboré avec les équipes GCP et Drive, car l’hébergement de fichiers malveillants est un problème que Google traite en permanence
    Ce n’est pas très différent d’autres endpoints sur lesquels Google autorise déjà les gens à déposer des données arbitraires

    • Ancien Googler, je connais mal l’équipe Go Dev Tools, mais parmi les grandes entreprises où j’ai travaillé, ou dont de proches amis m’ont parlé, Google est probablement l’une des meilleures pour ce genre de collaboration interne
      Google sait très bien gérer une infrastructure via des équipes centrales et la partager à l’échelle de l’entreprise. Sauf pour les applis de messagerie. Et, pure spéculation de ma part, l’équipe Go utilise probablement un stockage interne de blobs, avec sans doute une équipe d’infrastructure interne qui gère automatiquement la lutte contre les abus et l’analyse des fichiers
  • Il y a aussi pas mal de projets non Python sur PyPI
    Python a besoin de pouvoir distribuer des wheels, des binaires compilés, car les utilisateurs peuvent ne pas être en mesure de compiler le code des bibliothèques
    Ce code est souvent écrit en C, mais Golang[1] est aussi possible ; je n’ai pas trouvé d’exemple, mais il me semble avoir déjà vu cela utilisé pour distribuer des applications, pas seulement des bibliothèques
    Écrire une appli en C, la mettre sur PyPI et dire aux utilisateurs de l’installer avec pip install, c’est assez élégant
    [1] https://github.com/popatam/gopy_build_wheel_example

    • Même si l’on ajoutait par exemple une exigence d’utilisation de Python, quelqu’un de malveillant n’aurait-il pas seulement besoin de fournir un stub Python minimal pour s’y conformer ?
      Ce serait comme si, sous Linux, ls était écrit en Python ; mieux vaut probablement ne pas jouer à ce jeu-là
    • Cet usage aurait été bien plus utile avant que pip n’exige d’être dans un environnement comme venv/virtualenv/pipenv/pyenv pour télécharger un paquet
    • Il y a aussi pip install cmake, et des binaires propriétaires sont possibles, comme avec pip install nvidia-cudnn-cu12
    • Ces derniers temps, j’utilise beaucoup PyPI pour des outils non Python comme FFmpeg et Eigen
      C’est l’une des raisons pour lesquelles j’ai pu complètement abandonner Homebrew
  • C’est peut-être naïf, mais je ne vois pas en quoi c’est différent de téléverser des fichiers dans un dépôt GitHub
    La seule différence est-elle qu’il faut créer un compte sur GitHub ? On peut aussi stocker des données arbitraires sur GitHub, et il n’y a pas de limite de 500 Mo

    • GitHub impose des limites de requêtes assez strictes aux requêtes anonymes
  • Le système de modules de CUE est enfin en train de sortir, et son MVS est similaire à celui de Go, mais il est construit sur une infrastructure OCI
    Si la gestion des dépendances vous intéresse, voici quelques liens utiles
    proposition : https://github.com/cue-lang/proposal/tree/main/designs/modul...
    registry personnalisé : https://cuelang.org/docs/tutorial/working-with-a-custom-modu...
    feuille de route : https://github.com/orgs/cue-lang/projects/10/views/8
    les modules sont activés par défaut depuis la 0.9.0-alpha-5 : https://github.com/cue-lang/cue/releases/tag/v0.9.0-alpha.5
    Dans Go Sum, le projet Trillian sous-tend le journal de transparence : https://github.com/google/trillian
    CUE prévoit de s’appuyer sur des options OCI comme les attestations

    • Je ne vois pas le rapport avec l’article en lien
  • J’ai joué avec l’idée de m’appuyer sur le proxy golang et sumdb — autrement dit de les détourner — pour créer gratuitement un journal de transparence des sommes de contrôle d’URL arbitraires
    https://getsum.pub/

  • C’est peut-être moi qui suis bête, mais je ne vois pas exactement où est le problème ici
    Le fait que le proxy mette en cache des dépôts non Go est peut-être un peu du gaspillage, mais même sans ça, ne pourrait-on pas de toute façon lui faire mettre en cache des dépôts Go et donc lui faire stocker des données arbitraires ?
    À moins que quelque chose m’échappe, ça ressemble vraiment à un non-événement

    • Je ne pense pas qu’il t’échappe quoi que ce soit
      La nouveauté ici semble se résumer au fait qu’un proxy public non sécurisé accepte de relayer des choses et les sert publiquement de manière non sécurisée
      L’article dit que certains réseaux surveillés pourraient faire davantage confiance aux URL du proxy golang qu’à des URL web arbitraires, ce qui permettrait de contourner des filtres de réputation, etc., mais il existe déjà plusieurs méthodes de ce genre et celle-ci ne paraît pas particulièrement spéciale
  • Hors sujet, mais le domaine et son jeu de mots inquiétant m’ont poussé à vérifier put.as, et c’était globalement ce à quoi je m’attendais

    • https://put.as/ légèrement NSFW
    • J’ai fait l’erreur de l’ouvrir au travail
    • C’est bien un mot espagnol au pluriel. Mais…
  • Problème déjà connu : https://github.com/golang/go/issues/31866

    • Ce correctif aiderait à éviter les erreurs, mais quelqu’un qui veut le faire intentionnellement n’a-t-il pas simplement à ajouter des fichiers .mod et .go à la racine ?
    • Que Marwan soit présent dans cette issue n’a rien d’étonnant
      Aaron et lui ont créé Athens et, à ma connaissance, Marwan a écrit la première implémentation du protocole de téléchargement Go sur laquelle Athens s’est appuyé
      Ce qui est intéressant dans cette issue, c’est qu’Athens utilise déjà la commande go mod download -json, mentionnée comme vérification préalable pour la validation des modules
      En gros, si le dépôt passe comme module compréhensible par la commande de modules Go, Athens le servira
      Pour le dire en termes plus proscrits, il faut pouvoir créer une version de module, une pseudo-version et +incompatible, et ce module ainsi que ses dépendances doivent produire des sommes de contrôle valides
      Les sommes de contrôle de modules concernent actuellement le .mod et tous les fichiers, ainsi que chaque dépendance récursivement
      Donc, comme le dit l’auteur, avec un programme Go basique, on dispose par conception de beaucoup d’espace pour des fichiers arbitraires
  • Le W3C a posé les bases pour que tout sur le Web puisse être fortement mis en cache, et il est étrange qu’il y ait si peu de caches proxy génériques
    Les éditeurs envoient-ils des réponses avec un Cache-Control: max-age court ou Vary: Cookie sans que ce soit nécessaire ?
    Les FAI paient-ils trop cher le transit par rapport au peering ?

    • En général, il n’y a aucun moyen de garantir que le cache n’a pas modifié le contenu
      Par exemple, sur les sites qui ne sont pas en HTTPS, un proxy de FAI peut injecter des publicités
      Les téléchargements de logiciels ont généralement des signatures et des sommes de contrôle, mais ce genre de garanties est rare pour du contenu arbitraire