Pierre-Olivier Mercier
9862b5aa22
All checks were successful
continuous-integration/drone/push Build is passing
208 lines
9.4 KiB
Markdown
208 lines
9.4 KiB
Markdown
---
|
||
title: Post-mortem cryptominer on CI infrastructure
|
||
date: !!timestamp '2022-03-06 19:32:00'
|
||
tags:
|
||
- security
|
||
- cryptominer
|
||
- continuous integration
|
||
---
|
||
|
||
Après les [grandes plateformes de
|
||
cloud](https://stackoverflow.com/questions/63629722/gcp-has-suspended-my-machine-and-says-im-mining-cryptocurrency-how-do-i-fix-th),
|
||
puis [d'intégration
|
||
continue (CI)](https://therecord.media/github-investigating-crypto-mining-campaign-abusing-its-server-infrastructure/),
|
||
se plaignant des cryptomineurs, il semble que la course à leur
|
||
déploiement dans les petites installations de CI soit lancée.
|
||
|
||
Mon infrastructure a été la cible d'un hacker individuel hier, m'obligeant à
|
||
changer moi aussi quelques éléments de configuration pour m'adapter à ce
|
||
nouveau contexte.
|
||
|
||
<!-- more -->
|
||
|
||
# Une sacrée surprise pour démarrer un beau samedi
|
||
|
||
Dans ma quête de séparation des GAFAM et de contrôle de mes données
|
||
personnelles, j'héberge depuis de nombreuses années une forge qui
|
||
devient de plus en plus centrale pour mes projets.
|
||
|
||
Il y a quelques années, je lui ai notamment ajouté une plateforme
|
||
d'intégration continue : d'abord uniquement sur mon architecture de
|
||
prédilection : arm64, puis voyant tous les bienfaits de la CI, j'ai
|
||
ajouté une première machine amd64, pour les quelques déploiements que
|
||
j'avais besoin avec cette architecture. Ma principale utilisation est
|
||
la composition de documents avec `pandoc`, qui n'est pas disponible
|
||
sur d'autres architectures.
|
||
|
||
|
||
# Que s'est-il passé ?
|
||
|
||
Samedi matin, alors que je voulais lancer une tâche de composition de
|
||
document, via Drone, je constatais que la tâche ne se lançait
|
||
pas. Comme il s'agit d'un machine premier prix, elle a tendance à se mettre en défaut
|
||
facilement. Je me connecte donc dessus et constate qu'une tâche est en
|
||
cours, pourtant rien n'est affiché dans mon interface de DroneCI.
|
||
|
||
Je regarde directement les journaux du conteneur afin de déterminer de
|
||
quel projet il s'agit mais je ne le reconnais pas : il s'agit d'un
|
||
conteneur `ubuntu` (alors que la plupart de mes conteneurs utilisent
|
||
`alpine`), et la dernière commande ne log rien et utilise `tensorflow`.
|
||
Cela ne correspond vraiment à aucun des projets que j'héberge sur ma
|
||
forge.
|
||
|
||
Comme j'ai déjà été victime de bots de spam sur mon instance Gitea,
|
||
mon premier réflexe fut de consulter la liste des utilisateurs
|
||
nouvellement inscrits, pour voir s'il n'y avait pas quelqu'un que je
|
||
ne reconnaissais pas.
|
||
|
||
Sur la page listant les comptes utilisateurs, je constate la présence
|
||
d'un utilisateur fraîchement enregistré, j'apprends qu'il s'est
|
||
connecté via GitLab et qu'il s'est déjà créé un dépôt. En parcourant
|
||
son dépôt, je m'aperçois que c'est bien le dépôt qui est en cours
|
||
d'exécution.
|
||
|
||
|
||
# Ma première réaction
|
||
|
||
Maintenant que l'origine du problème était établie, j'ai commencé par
|
||
désactiver le compte de l'utilisateur puis j'ai stoppé brutalement son
|
||
conteneur, en utilisent `docker stop`.
|
||
|
||
Avant de désactiver le dépôt sur Drone, j'ai constaté que le pirate
|
||
avait mis en place une tâche `cron` chaque heure pour relancer sa tâche
|
||
de CI. Les tâches étant automatiquement arrêtées au bout de 60
|
||
minutes, il avait donc toujours une tâche en cours d'exécution sur
|
||
l'infrastructure.
|
||
|
||
|
||
# Mon analyse minutieuse de la situation
|
||
|
||
![Dyn repository](repository.png)
|
||
|
||
Une fois le calme revenu, vient l'analyse du code et du contexte. Le
|
||
code, qui est récupéré depuis un dépôt sur GitLab montre plusieurs
|
||
scripts shell obfusqués par un simple encodage base64. Une fois
|
||
décodé, on voit tout de suite que l'on a affaire à un cryptominer
|
||
basique. Il ne semble pas y avoir un groupe bien rodé derrière cette
|
||
intrusion, mais bien un individu qui explore les possibilités.
|
||
|
||
![Shell script](base64.png)
|
||
|
||
En remontant la piste jusqu'au portefeuille unique, on voit que les
|
||
sommes perçues sont de l'ordre de 25$ par semaine, et cela fait une
|
||
vingtaine de jours que notre protagoniste est actif.
|
||
|
||
Une rapide analyse des journaux a montré qu'il était arrivé sur mon
|
||
instance en recherchant précisément sur Google un fichier
|
||
`.drone.yml`. On le voit ensuite se connecter, via gitlab puis passer
|
||
l'étape de liaison à un éventuel compte existant protégée par un
|
||
captcha. Les temps entre chaque requête et les assets téléchargés
|
||
laissent supposer qu'il s'agit bien d'un humain et que l'on a donc pas
|
||
(encore) affaire à un script automatique.
|
||
|
||
Voici le cheminement exact :
|
||
|
||
1. arrivée sur git.nemunai.re depuis Google, sur un fichier `.drone.yml` ;
|
||
1. connexion via GitLab, via le lien OAuth2 disponible ;
|
||
1. création puis activation de son compte sur git.nemunai.re ;
|
||
1. recherche d'un dépôt sur la page d'accueil (mais apparemment j'en ai trop) ;
|
||
1. nouvelle arrivée depuis Google, sur un autre fichier `.drone.yml`, a priori le suivant dans la liste ;
|
||
1. passage sur la partie publique de DroneCI ;
|
||
1. connexion à Drone → création de l'utilisateur ;
|
||
1. retour sur Gitea pour créer le dépôt (il choisit la migration depuis GitLab) ;
|
||
1. synchronisation des dépôts sur Drone ;
|
||
1. activation de son dépôt ;
|
||
1. déclenchement manuel d'une synchronisation du dépôt afin de récupérer un nouveau commit, ce qui lance sa première tâche dans Drone ;
|
||
1. ajout de la tâche `cron` dans Drone pour relancer sa tâche chaque heure.
|
||
|
||
Tout cela s'est déroulé dans un intervalle de 6 minutes, ce n'était
|
||
donc pas un robot, mais bien un humain qui savait ce qu'il faisait.
|
||
|
||
---
|
||
|
||
|
||
Mon instance Gitea est reliée à un système de statistique de
|
||
fréquentation (Umami, qui me permet de ne pas avoir de bandeau
|
||
RGPD). Dessus, on voit effectivement la connexion depuis GitLab.com et
|
||
l'accès au dépôt déclencheur, malgré le fait que l'utilisateur et le
|
||
dépôt soient privés. D'après Umami, notre visiteur serait
|
||
[indonésien](https://ipinfo.io/AS7713). Une rapide recherche sur le nom du compte GitLab qui a
|
||
effectué la connexion montre également un profil LinkedIn indonésien
|
||
d'un Deep Learning Specialist, ayant fait ses premiers pas sur GitLab
|
||
il y a environ 2 semaines... Cependant le dépôt du crytominer n'est
|
||
pas hébergé par son compte. On peut donc se demander s'il s'agit de la
|
||
même personne ou si son compte GitLab a été compromis.
|
||
|
||
Dans le doute, j'ai fait un rapport d'abus sur GitLab pour cet utilisateur.
|
||
|
||
![Abuse filled to GitLab](abuse.png)
|
||
|
||
Je n'ai pas été contacté par GitLab suite à ce rapport, et je ne
|
||
pensais d'ailleurs pas qu'il serait traité le jour même, considérant
|
||
que d'une part il ne s'agit pas d'un problème critique comme de la
|
||
pédo-pornographie ou similaire, et que, d'autre part, cela nécessitait
|
||
sans doute de faire valider mon analyse par un ingénieur qualifié.
|
||
|
||
Toujours est-il que les deux comptes ont été bloqués dans les deux heures.
|
||
|
||
|
||
# Remédiation
|
||
|
||
Estimant que j'avais une suffisamment bonne vision sur la situation et son
|
||
contexte, je me suis enfin attelé à trouver les solutions pour remédier aux
|
||
différents problèmes.
|
||
|
||
Tout d'abord mon instance Gitea a pour vocation d'être accessible à tous, afin
|
||
que n'importe qui puisse facilement contribuer, notamment en faisant des
|
||
rapports de bug ou des pull-requests sur mes projets.
|
||
|
||
Considérant le faible taux de création de fork, face aux problèmes que
|
||
cela peut engendrer -- notamment via la création de pull-requests
|
||
indésirables qui, en remplaçant la logique du `.drone.yml`, pourrait
|
||
permettre de lancer un cryptominer jusqu'à l'expiration du délais
|
||
d'exécution de la tâche -- j'ai donc décidé de changer le nombre maximal
|
||
de dépôt que peut créer un nouvel utilisateur de 1 à 0. J'attendrai
|
||
désormais qu'un utilisateur me sollicite afin de lui augmenter sa
|
||
limite individuelle.
|
||
|
||
Concernant Drone, j'ai cherché une option permettant de limiter les
|
||
utilisateurs autorisés à en faire usage.
|
||
|
||
Il se trouve qu'il y a justement une option :
|
||
[`DRONE_USER_FILTER`](https://docs.drone.io/server/reference/drone-user-filter/)
|
||
qui permet de lister les groupes d'utilisateurs et/on les utilisateurs
|
||
individuels qui sont autorisés à se connecter.
|
||
|
||
Cette option répond exactement à mon besoin, je l'ai donc ajoutée aux options de
|
||
déploiement de mon instance de DroneCI.
|
||
|
||
Pour être exhaustif, c'est également à ce moment que j'ai désactivé le
|
||
dépôt du cryptominer et supprimé l'utilisateur malveillant de Drone,
|
||
car il aurait encore pu s'y connecter.
|
||
|
||
|
||
# Conclusion
|
||
|
||
J'ai été très agréablement surpris de la réactivité des équipes de
|
||
GitLab : pour avoir désactivé les comptes contrevenants, malgré le
|
||
fait que cela ne concernenait pas directement un abus des conditions
|
||
d'utilisation de leur plateforme.
|
||
|
||
Je ne sais pas s'ils sont allés jusqu'à contacter tous les comptes
|
||
pour lesquels l'utilisateur malveillant a fait usage de leur lien
|
||
OAuth 2. D'autres infrastructures sont, en effet, toujours utilisées
|
||
par ce hacker d'après les activités de son porte-feuille.
|
||
|
||
Une chose est sûre, plus que jamais, il faut bien faire attention aux
|
||
options que l'on choisit lorsque l'on souhaite exposer un service au
|
||
public, car on ne s'attend pas toujours à ce qui peut se passer. Et
|
||
bien que cet utilisateur ait été bloqué, rien ne l'empêche de
|
||
continuer de chercher de nouvelles forges à exploiter.
|
||
|
||
En complément, citons ce récent rapport [publié par le NCC
|
||
Group](https://research.nccgroup.com/2022/01/13/10-real-world-stories-of-how-weve-compromised-ci-cd-pipelines/)
|
||
à propos des principaux vecteurs de compromissions des environnements
|
||
d'intégration continue.
|
||
|
||
![Blocked users](blocked-users.png)
|