docs: Einbruch in den Gitea vom 12.08.2026 -- Hergang, Messwerte, Reparatur
Kein Ausfall, kein Datenverlust, kein laufender Schadcode -- aber ein echter Schreibzugriff von aussen. Um 21:13:56 rief jemand /api/internal/manager/add-logger auf, legte einen Datei-Logger auf die .gitconfig, liess die Nutzlast als Logzeile hineinschreiben und entfernte den Logger eine Sekunde spaeter wieder. Eingeschleust war ein packObjectsHook, der bei JEDEM Klonen einen Monero-Miner nachgeladen und per crontab verankert haette. WARUM TROTZDEM NICHTS LIEF: die Nutzlast beginnt mit "[uploadpack]\n", wobei das \n als TEXT in der Datei steht statt als Umbruch. Git bricht mit "bad config line 23" ab, der Hook wurde nie gueltig. Nachgemessen: kein Prozess, keine Datei, keine crontab-Zeile, nichts in den Repos, kein Docker-Socket im Container, keine fremden Konten oder Token. Der einzige Schaden war, dass alle Git-Operationen ausfielen -- daran ist es aufgefallen. DER WEG HINEIN: Gitea lag mit 0.0.0.0:3000 direkt im Internet, am Proxy vorbei. Damit war /api/internal/* von aussen erreichbar, geschuetzt nur durch das INTERNAL_TOKEN. Woher der Angreifer es hatte, liess sich NICHT klaeren -- es steht in keinem Repo, und RDP war es nicht (2756 Fehlversuche in 14 Tagen, aber alle 33 erfolgreichen Anmeldungen von der IP des Betreibers). Genau deshalb ist die Antwort nicht "Token tauschen", sondern den Weg zumachen. GEAENDERT: .gitconfig gesaeubert; Port 3000 gar nicht mehr veroeffentlicht; NPM haengt jetzt mit in gitea_default und spricht gitea:3000 an (dafuer musste der nginx-Resolver auf Dockers DNS 127.0.0.11 umgestellt werden); INTERNAL_TOKEN und SECRET_KEY getauscht (gefahrlos: 2FA und Anmeldequellen jeweils 0); /api/internal/ gibt am Proxy 404; frisches Abbild statt des elf Tage alten. Gegengeprueft: Port 3000 von aussen 000, /api/internal/ 404, git.d4rkst3r.de 200, LFS-Direktiven alle drei erhalten, Push geht beim ersten Versuch, Repos identisch mit den lokalen Klonen. DIE LEHRE: der offene Port war nie noetig -- der Proxy haette von Anfang an uebers interne Netz gehen koennen. Und aufgefallen ist es nicht, weil etwas ueberwacht wurde, sondern weil der Angreifer geschludert hat. Haette er sein \n richtig gesetzt, liefe der Miner noch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,128 @@
|
|||||||
|
# Einbruch in den Gitea, 12.08.2026
|
||||||
|
|
||||||
|
Kein Ausfall, kein Datenverlust, kein laufender Schadcode — aber ein echter
|
||||||
|
Schreibzugriff von außen. Das hier ist der Hergang, so wie er **gemessen**
|
||||||
|
wurde, und was daraus geändert wurde.
|
||||||
|
|
||||||
|
## Was passiert ist
|
||||||
|
|
||||||
|
```
|
||||||
|
21:13:56 POST /api/internal/manager/add-logger -> 200 OK
|
||||||
|
21:13:57 POST /api/internal/manager/remove-logger/... -> 200 OK
|
||||||
|
21:13:57 /data/gitea/home/.gitconfig neu geschrieben
|
||||||
|
```
|
||||||
|
|
||||||
|
Der Angreifer hat über Giteas **interne Manager-API** einen Datei-Logger auf
|
||||||
|
die `.gitconfig` gelegt, seine Nutzlast als Logzeile hineinschreiben lassen und
|
||||||
|
den Logger eine Sekunde später wieder entfernt. Der Vorgang räumt hinter sich
|
||||||
|
auf; ohne den Nebeneffekt wäre er unbemerkt geblieben.
|
||||||
|
|
||||||
|
Eingeschleust wurde ein `packObjectsHook` — ein Kommando, das Git bei jedem
|
||||||
|
`upload-pack` (also bei jedem Klonen und Ziehen) ausführt. Dekodiert:
|
||||||
|
|
||||||
|
- lädt `xmrig` von `111.90.158.58` bzw. `111.90.139.202`
|
||||||
|
- tarnt ihn als `/var/tmp/.gt_sys_health` mit Konfiguration daneben
|
||||||
|
- schürft gegen `gulf.moneroocean.stream:443`/`:80`, Worker-ID `gitea`,
|
||||||
|
Wallet `89wM69z64AMhAKAY…`
|
||||||
|
- verankert sich per `crontab` (`*/3 * * * *` und `@reboot`) und `/etc/cron.d/gt_sys`
|
||||||
|
- killt konkurrierende Miner und eigene Altfassungen
|
||||||
|
|
||||||
|
## Warum trotzdem nichts lief
|
||||||
|
|
||||||
|
**Die Nutzlast hat sich selbst zerschossen.** Die eingeschleuste Zeile beginnt
|
||||||
|
mit `[uploadpack]\n` — wobei das `\n` als **Text** in der Datei steht und nicht
|
||||||
|
als Zeilenumbruch. Git kann die Datei damit nicht lesen, bricht mit
|
||||||
|
`fatal: bad config line 23` ab, und der Hook wurde nie gültig.
|
||||||
|
|
||||||
|
Nachgemessen, im Container wie auf dem Wirt:
|
||||||
|
|
||||||
|
| | Befund |
|
||||||
|
|---|---|
|
||||||
|
| Miner-Prozess | keiner |
|
||||||
|
| `/var/tmp/.gt_*`, `/tmp/.gt_*` | nichts |
|
||||||
|
| crontab, `/etc/cron.d` | unverändert |
|
||||||
|
| Nutzlast in Repos | keine |
|
||||||
|
| Docker-Socket im Container | **nicht gemountet** — kein Ausbruch auf den Wirt |
|
||||||
|
| Gitea-Konten | eines, das eigene |
|
||||||
|
| Fremde Token oder SSH-Schlüssel | keine |
|
||||||
|
|
||||||
|
Der einzige Schaden war, dass **alle Git-Operationen ausfielen** — inklusive
|
||||||
|
der Pushes aus diesem Projekt. Der Angriff ist nur daran aufgefallen.
|
||||||
|
|
||||||
|
## Der Weg hinein
|
||||||
|
|
||||||
|
Gitea lag mit `0.0.0.0:3000` **direkt im Internet**, am Reverse-Proxy vorbei —
|
||||||
|
`http://88.218.224.10:3000/` antwortete mit 200. Damit war auch
|
||||||
|
`/api/internal/*` von außen erreichbar; geschützt nur durch das
|
||||||
|
`INTERNAL_TOKEN`.
|
||||||
|
|
||||||
|
**Woher der Angreifer das Token hatte, ließ sich nicht klären.** Es steht in
|
||||||
|
keinem Repo, und RDP war es nicht (siehe unten). Bleibt eine Lücke in Gitea
|
||||||
|
1.26.4 oder ein Leck, das hier nicht sichtbar ist. Genau deshalb ist die
|
||||||
|
Antwort nicht „Token tauschen", sondern **den Weg zumachen**.
|
||||||
|
|
||||||
|
### RDP: beschossen, nicht geknackt
|
||||||
|
|
||||||
|
Cloudflare meldete offene RDP-Server. Nachgesehen im Sicherheitsprotokoll,
|
||||||
|
14 Tage:
|
||||||
|
|
||||||
|
```
|
||||||
|
2756 fehlgeschlagene Anmeldungen, verteilt auf viele IPs
|
||||||
|
33 erfolgreiche von aussen -- alle von EINER IP, dem Anschluss des Betreibers
|
||||||
|
```
|
||||||
|
|
||||||
|
RDP war also **nicht** der Einbruchsweg. NLA ist aktiv. Trotzdem gehört der
|
||||||
|
Zugang eingegrenzt — 2756 Versuche sind ein Dauerbeschuss.
|
||||||
|
|
||||||
|
## Was geändert wurde
|
||||||
|
|
||||||
|
1. **`.gitconfig` gesäubert** — die zwei vergifteten Zeilen raus, 22 Zeilen
|
||||||
|
übrig. Git funktioniert wieder beim ersten Versuch.
|
||||||
|
2. **Port 3000 ist weg.** Nicht auf localhost gebunden, sondern **gar nicht
|
||||||
|
mehr veröffentlicht**. Von außen: keine Antwort.
|
||||||
|
3. **Der Proxy geht jetzt durchs interne Netz.** NPM hängt zusätzlich in
|
||||||
|
`gitea_default` und spricht `gitea:3000` an. Dafür musste der nginx-Resolver
|
||||||
|
auf Dockers eigenen DNS (`127.0.0.11`) umgestellt werden — der alte stammte
|
||||||
|
noch aus der Zeit, als NPM nur im `bridge`-Netz hing.
|
||||||
|
4. **`INTERNAL_TOKEN` und `SECRET_KEY` getauscht.** Gefahrlos hier, weil weder
|
||||||
|
2FA noch externe Anmeldequellen eingerichtet sind (beides nachgezählt: 0).
|
||||||
|
5. **`/api/internal/` gibt am Proxy jetzt 404.** Dritte Sperre: selbst wenn das
|
||||||
|
Token erneut leckt, ist der Weg von außen zu. Gitea ruft diese Pfade nur
|
||||||
|
containerintern über localhost.
|
||||||
|
6. **Frisches Abbild.** Der Container lief elf Tage mit dem Stand von damals —
|
||||||
|
`gitea/gitea:latest` heißt nicht, dass das Laufende aktuell ist.
|
||||||
|
|
||||||
|
Gegengeprüft, beides:
|
||||||
|
|
||||||
|
```
|
||||||
|
Port 3000 von aussen -> 000 (zu)
|
||||||
|
/api/internal/manager/add-logger -> 404 (gesperrt)
|
||||||
|
https://git.d4rkst3r.de/ -> 200
|
||||||
|
https://git.d4rkst3r.de/api/v1/version -> 200
|
||||||
|
grosse Uploads (LFS): 3 von 3 Direktiven noch da
|
||||||
|
Push aus diesem Projekt: geht, beim ersten Versuch
|
||||||
|
Repos gegen die lokalen Klone: identisch
|
||||||
|
```
|
||||||
|
|
||||||
|
## Was noch offen ist
|
||||||
|
|
||||||
|
- **Alle API-Token und Webhook-Geheimnisse als geleakt behandeln** und
|
||||||
|
tauschen: `GITEA_TOKEN`, das Token `Claude`, Devlog- und Gitea-Webhooks.
|
||||||
|
Nicht weil ein Diebstahl belegt ist, sondern weil er nicht ausgeschlossen
|
||||||
|
werden kann.
|
||||||
|
- **Admin-Passwort ändern.**
|
||||||
|
- **RDP eingrenzen** — siehe unten.
|
||||||
|
- **Beweise** liegen unter `C:\backup\vorfall-2026-08-12\`: die vergiftete
|
||||||
|
`.gitconfig`, die ursprüngliche `app.ini`, das vollständige Gitea-Log
|
||||||
|
(24 MB) und die alte Compose-Datei.
|
||||||
|
|
||||||
|
## Die Lehre, die über diesen Fall hinausgeht
|
||||||
|
|
||||||
|
Der offene Port war **nie nötig**. Der Proxy hätte von Anfang an über das
|
||||||
|
interne Netz gehen können; die Veröffentlichung war Bequemlichkeit beim
|
||||||
|
Einrichten. Sie stand elf Tage, und in dieser Zeit hat jemand sie gefunden.
|
||||||
|
|
||||||
|
Und: aufgefallen ist es nicht, weil etwas überwacht wurde, sondern weil der
|
||||||
|
Angreifer geschludert hat. **Hätte er sein `\n` richtig gesetzt, liefe der
|
||||||
|
Miner noch** — und die einzige Spur wäre eine erhöhte CPU-Last gewesen, die
|
||||||
|
niemandem auffällt.
|
||||||
Reference in New Issue
Block a user