Abus de l’infrastructure Go
(reverse.put.as)- 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@$versionré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 surproxy.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-coreapparaissait très souvent dans la tablemodulesgithub.com/homebrew/homebrew-core: 39 438github.com/Homebrew/homebrew-core: 30 896github.com/concourse/concourse: 25 372github.com/openshift/release: 24 065github.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.modou 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 foisexample.com/Metexample.com/msur des systèmes de fichiers insensibles à la casse
- Les majuscules sont encodées avec
github.com/Edu4rdSHL/rust-headless-chromeapparaî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
/lookuplorsqu’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
$versionde$module, les lignesgo.sumet une description d’arbre signée
- Il renvoie le numéro d’enregistrement dans le journal pour la
- Lorsqu’on interroge une pseudo-version de
github.com/homebrew/homebrew-core, l’enregistrement de checksum et le hash dego.modsont 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 appellookupa servi à vérifier s’il était enregistré - Une requête sur
@latestrenvoie une erreur indiquant qu’il ne s’agit pas d’une version canoniquebad request: version "latest" is not canonical
- Une requête sur
@v0.0.0renvoie une erreur indiquant que cette révision est inconnuenot 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/@latestrenvoyait 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
lookuprenvoyait une erreur disant que la révisionv0.0.0était inconnue, mais la checksum database l’a tout de même enregistré avec une pseudo-versiongithub.com/gdbinit/readmem|v0.0.0-20131006075740-407cb0a56933|2024-05-24T16:45:35.88456Z
- Le
@latestdeproxy.golang.orgrenvoyait 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
GOPROXYprivé - 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
- en supposant l’absence de
- Une attaque DoS contre
proxy.golang.orgpeut ê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
@latestpermet de trouver la dernière version d’un module donné - le payload peut être un simple fichier, ou être caché dans
go.modou dans des fichiers source Go - pour éviter l’usage d’un dépôt unique, on peut utiliser un module DGA
- une requête
Flux de téléchargement du C2
- Pour recevoir des commandes, un implant peut suivre les étapes suivantes
- requête
https://proxy.golang.org/module_path/@latest - extraction de la pseudo-version ou de la version depuis le résultat JSON
- téléchargement du zip via
https://proxy.golang.org/module_path/@v/version.zip - extraction du contenu du zip puis parsing des commandes
- requête
- Ce flux est suffisamment simple pour être implémenté en moins de 300 lignes de code Go
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
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...
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)
On peut aussi encoder un fichier en Base64 dans une chaîne de code Python
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
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
Ce serait comme si, sous Linux,
lsétait écrit en Python ; mieux vaut probablement ne pas jouer à ce jeu-làpipn’exige d’être dans un environnement comme venv/virtualenv/pipenv/pyenv pour télécharger un paquetpip install cmake, et des binaires propriétaires sont possibles, comme avecpip install nvidia-cudnn-cu12C’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
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
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/
Si l’on veut juste un journal de transparence public, l’instance publique rekor du projet sigstore est bien plus adaptée
https://www.sigstore.dev/
https://docs.sigstore.dev/logging/overview/
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
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
Problème déjà connu : https://github.com/golang/go/issues/31866
.modet.goà la racine ?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 modulesEn 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 validesLes sommes de contrôle de modules concernent actuellement le
.modet tous les fichiers, ainsi que chaque dépendance récursivementDonc, 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-agecourt ouVary: Cookiesans que ce soit nécessaire ?Les FAI paient-ils trop cher le transit par rapport au peering ?
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