docs: documente l'incident cryptominer Gitea et détails infra découverts
Incident du 2026-09-14 : injection de packObjectsHook via l'API interne Gitea pour déployer un mineur Monero (XMRig). Contenu et nettoyé le 2026-09-22. Voir incidents/2026-09-14-gitea-cryptominer.md pour le détail complet (analyse, IOC, remédiation, reste à faire). Ajout également des détails techniques sur le setup Gitea (containers, volumes, réseau) découverts pendant l'investigation. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
78454b6fa2
commit
8f584a8672
@@ -4,9 +4,9 @@ Lorsque tu interroges le serveur, ne lances que des commandes en LECTURE SEULE (
|
||||
|
||||
## Accès
|
||||
|
||||
- **SSH**: `ssh cloud@sans.pub`
|
||||
- **SSH**: `ssh zuck@sans.pub` (alias `~/.ssh/config` : `Host sans.pub`) — sudo sans mot de passe. Note : ce document mentionnait auparavant un utilisateur `cloud`, à vérifier/clarifier (voir `install-serveur.md`, le compte créé est `zuck`).
|
||||
- **CapRover**: https://captain.cloud.sans.pub
|
||||
- **Utilisateur**: cloud (gestion Docker/Caprover)
|
||||
- **Gitea**: `git.sans.pub` (port SSH 2242, `User git` dans `~/.ssh/config`)
|
||||
|
||||
## Infrastructure
|
||||
|
||||
@@ -78,6 +78,18 @@ Aucun service en erreur connu (vérifié 2026-02-19). `mail` tourne correctement
|
||||
|
||||
**Prérequis** : définir `CAPROVER_PASSWORD` dans `.env` (voir `.envrc`)
|
||||
|
||||
## Gitea — détails techniques
|
||||
|
||||
- **Containers Docker** (Swarm) : `srv-captain--gitea.1.*` (gitea/gitea:1.25.4) + `srv-captain--gitea-db.1.*` (mysql:5.7)
|
||||
- **Volume de données** : `captain--gitea-data` → monté sur `/data` dans le container
|
||||
- Dépôts bare : `/data/git/repositories/<owner>/<repo>.git`
|
||||
- Config app : `/data/gitea/conf/app.ini` (contient des secrets en clair : `INTERNAL_TOKEN`, `LFS_JWT_SECRET`, mot de passe MySQL — jamais versionner ce fichier)
|
||||
- `.gitconfig` global (compte système utilisé par Gitea pour exécuter git) : `/data/gitea/home/.gitconfig`
|
||||
- Logs applicatifs : `/data/gitea/log/gitea.log` (rotation quotidienne en `.gz`)
|
||||
- **Réseau** : container Gitea sur `captain-overlay-network`, IP interne observée `10.0.1.75`. Le reverse-proxy CapRover (`captain-nginx`) apparaît côté Gitea sous l'IP `10.0.1.105` pour toutes les requêtes publiques (pas d'IP client réelle sans config `X-Forwarded-For`/trusted proxies).
|
||||
- **Connexion à la base MySQL depuis le host/container** : passer par `-h 127.0.0.1` (TCP), pas par défaut socket local (`localhost`) — le grant MySQL de l'utilisateur `gitea` ne couvre que `%`/TCP, pas `localhost`.
|
||||
- **⚠️ Voir `incidents/2026-09-14-gitea-cryptominer.md`** : l'API interne Gitea (`/api/internal/manager/add-logger`) a été exploitée pour injecter des `packObjectsHook` malveillants dans `.gitconfig`. Toujours vérifier ce fichier ne contient pas d'entrées `[uploadpack] packObjectsHook` inattendues.
|
||||
|
||||
## Suivi des tâches
|
||||
|
||||
Voir `todo.txt` (format todo.txt standard)
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
# Incident : mineur de cryptomonnaie via Gitea (packObjectsHook)
|
||||
|
||||
- **Statut** : contenu, remédiation en cours (rotation des secrets à finaliser)
|
||||
- **Date de compromission** : 2026-09-14 ~15:16-15:17
|
||||
- **Date de détection** : 2026-09-22 (via un `git clone` cassé)
|
||||
- **Impact** : exécution de code sur le serveur hébergeant Gitea (et potentiellement l'hôte partagé, ~40 services CapRover) ; minage de cryptomonnaie (Monero) actif pendant ~8 jours
|
||||
|
||||
## Découverte
|
||||
|
||||
Un `git clone` sur `git.sans.pub` échouait systématiquement (SSH et HTTPS) avec :
|
||||
|
||||
```
|
||||
remote: wget: server returned error: HTTP/1.1 404 Not Found
|
||||
remote: curl: (22) The requested URL returned error: 404
|
||||
fatal: fin de fichier prématurée
|
||||
fatal: fetch-pack : sortie d'index de pack invalide
|
||||
```
|
||||
|
||||
Le problème touchait **tous les dépôts**, pas un seul — signe d'un problème serveur global plutôt qu'un souci de repo.
|
||||
|
||||
## Analyse
|
||||
|
||||
`GIT_TRACE_PACKET=1` a montré que le serveur, juste après avoir annoncé `packfile`, envoyait sur le canal d'erreur (sideband 2) le texte `wget: ... 404` / `curl: ... 404` puis coupait la connexion — donc un script serveur s'exécute pendant la génération du pack, avant même l'envoi des données.
|
||||
|
||||
Cause trouvée dans `/data/gitea/home/.gitconfig` (dans le volume Docker `captain--gitea-data`) : **13 lignes `[uploadpack] packObjectsHook = sh /data/gitea/home/p0_XXXXXXXX.sh` injectées**, chacune maquillée en fin de ligne par un faux commentaire imitant une entrée de log Gitea :
|
||||
|
||||
```
|
||||
;#router: completed POST /api/internal/manager/add-logger for 10.0.1.105:XXXXX, 200 OK ...
|
||||
```
|
||||
|
||||
Cette technique fait croire, à la lecture rapide du fichier, qu'il s'agit d'une ligne de log et non de config active.
|
||||
|
||||
### Vecteur d'entrée (hypothèse la plus probable)
|
||||
|
||||
L'endpoint interne Gitea `/api/internal/manager/add-logger` (normalement réservé à un usage interne, protégé par `INTERNAL_TOKEN`) semble avoir été atteignable depuis l'extérieur via le reverse-proxy public (l'IP source `10.0.1.105` observée dans les logs correspond au conteneur `captain-nginx`, donc toutes les requêtes publiques passent par cette IP côté Gitea — la requête malveillante est arrivée par le chemin HTTP public normal). Ceci est cohérent avec des vulnérabilités connues de Gitea permettant l'injection de config git via cette API interne mal exposée.
|
||||
|
||||
**À vérifier/confirmer** : version exacte de Gitea au moment de l'attaque (`gitea/gitea:1.25.4`), CVE correspondant, et si `/api/internal/*` est bien bloqué au niveau du reverse-proxy CapRover.
|
||||
|
||||
## Charge malveillante
|
||||
|
||||
Les scripts injectés (`p0_c9cd952f.sh`, `p0_9759db49.sh`) :
|
||||
|
||||
1. Téléchargent un binaire déguisé : `https://sunnyeye.solarvest.my/health.bin` → sauvegardé sous `/data/gitea/.sys_health_s3`
|
||||
2. Écrivent une config **XMRig** (JSON encodé en base64 dans le script) pointant vers des pools Monero (`gulf.moneroocean.stream`, `pool.hashvault.pro`) avec une adresse de wallet dédiée
|
||||
3. Assurent la persistance :
|
||||
- via `crontab` (`*/3 * * * *` + `@reboot`) si `cron`/`crond` est disponible
|
||||
- sinon via une boucle `sh -c 'while sleep 180; do <watchdog>; done'` détachée (`nohup`)
|
||||
4. Se camouflent sous des noms plausibles : `.sys_health_s3`, `.gitea_cron_health`, `.wp_s2_cron`, `.gitea_watchdog.pid`
|
||||
|
||||
Le mineur tournait en continu depuis le 2026-09-14 (le conteneur Gitea n'avait pas redémarré depuis), consommant ~298% CPU / ~2.4 Go RAM au moment de la détection. Il a cessé de fonctionner correctement quand le domaine `sunnyeye.solarvest.my` s'est mis à répondre 404 — c'est cet échec de téléchargement qui a cassé les clones et permis la détection.
|
||||
|
||||
## Remédiation effectuée (2026-09-22)
|
||||
|
||||
- [x] Process du mineur et du watchdog tués (`kill -9` sur les PID hôte)
|
||||
- [x] Fichiers de persistance supprimés : `.sys_health_s3`, `.sys_health_s3.json`, `.sys_health_s3.log`, `.gitea_cron_health`, `.gitea_watchdog.pid`
|
||||
- [x] `.gitconfig` nettoyé des 13 lignes `packObjectsHook` injectées (backup conservé : `/data/gitea/home/.gitconfig.bak-20260922234958`)
|
||||
- [x] Clone git re-testé et fonctionnel
|
||||
- [x] Audit de la base Gitea (voir ci-dessous) : aucune autre persistance trouvée
|
||||
|
||||
## Audit Gitea (base de données) — résultat
|
||||
|
||||
Aucune anomalie détectée au-delà de l'injection `packObjectsHook` :
|
||||
|
||||
| Élément vérifié | Résultat |
|
||||
|---|---|
|
||||
| Comptes utilisateurs | 3 comptes (`pierre` admin, `Sans.pub`, `Duogeeks`), tous antérieurs à l'incident |
|
||||
| Clés SSH publiques | 3 clés, aucune ajout suspect autour du 09-14 |
|
||||
| Tokens d'accès personnels | 4 tokens, tous antérieurs et cohérents avec l'usage habituel |
|
||||
| Webhooks | 4 webhooks de déploiement CapRover, tous de 2020-2021 |
|
||||
| Sources de login externes | 1 seule, préexistante (2020) |
|
||||
| Collaborateurs sur les repos | Rien d'ajouté récemment |
|
||||
| Organisations | Aucune organisation suspecte |
|
||||
|
||||
**Conclusion** : l'attaque semble limitée à l'exécution de commande via le hook git (cryptojacking), sans prise de compte ni persistance au niveau applicatif Gitea. Mais l'attaquant avait un accès en lecture au système de fichiers du conteneur, donc à `app.ini` (secrets en clair) — voir rotation ci-dessous.
|
||||
|
||||
## Reste à faire
|
||||
|
||||
- [ ] Mettre à jour Gitea vers la dernière version patchée (partir de `1.25.4`)
|
||||
- [ ] Vérifier/restreindre l'accès à `/api/internal/*` au niveau du reverse-proxy (défense en profondeur, même si Gitea patch le bug)
|
||||
- [ ] Rotation des secrets (voir section dédiée plus bas / demander le détail à Claude si besoin)
|
||||
- [ ] Vérifier l'intégrité des autres services sur le même hôte (mouvement latéral) — rien trouvé à ce stade, mais l'hôte partagé fait tourner Vaultwarden, Nextcloud, mail
|
||||
- [ ] Envisager un scan antirootkit / vérification d'intégrité des binaires système sur l'hôte, vu la durée de la compromission (8 jours)
|
||||
- [ ] Revoir si `DOMAIN = localhost` dans `app.ini` (`[server]`) est correct ou un reliquat de config
|
||||
|
||||
## Indicateurs de compromission (IOC)
|
||||
|
||||
- Domaine : `sunnyeye.solarvest.my`
|
||||
- Fichiers : `p0_*.sh`, `.sys_health_s3`, `.sys_health_s3.json`, `.sys_health_s3.log`, `.gitea_cron_health`, `.gitea_watchdog.pid`, `.wp_s2_cron`, `.wp_s2_wd.pid`, `.s3w`
|
||||
- Chemins ciblés pour la persistance (dans cet ordre) : `/data/gitea`, `/data/git`, `/home/git`, `/var/tmp`, `/dev/shm`, `/tmp`, `$HOME`
|
||||
- Pattern de commentaire d'obfuscation dans les configs git : `;#router: completed POST /api/internal/manager/add-logger for <ip>, 200 OK ...`
|
||||
Reference in New Issue
Block a user