---
title: Post-mortem cryptominer on CI infrastructure
date: !!timestamp '2022-03-06 19:32:00'
image: /fr/post/post-mortem-cryptominer-on-ci-infrastructure/og.webp
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)
