Test-NetConnection vom Heimrechner: TcpTestSucceeded False. Das war der letzte offene Punkt der Nacht. Die Messung vom Server aus taugte dafuer NICHT: Verkehr an die eigene oeffentliche Adresse durchlaeuft die Eingangsregeln nicht und meldete faelschlich "erreichbar". Zweimal in derselben Nacht in dieselbe Falle getappt, einmal bei Port 3000 und einmal bei 3389 -- beide Male hat erst die Messung von DRAUSSEN das Urteil gebracht. Dazu: sshd nimmt nur noch Schluessel (Passwort UND keyboard-interactive aus, vor dem ersten Match-Block), RDP per Firewall gesperrt, Zugang ueber tools/tunnel-einrichten.ps1 als Hintergrundaufgabe. Seitdem laufen die Angriffe ins Leere: "invalid user root ... [preauth]", und alle 12 erfolgreichen Anmeldungen kommen von einer einzigen IP. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
171 lines
7.0 KiB
Markdown
171 lines
7.0 KiB
Markdown
# 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~~ — erledigt, 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.
|
|
|
|
---
|
|
|
|
## Nachtrag 13.08.2026: RDP ist zu, der Zugang laeuft ueber einen Tunnel
|
|
|
|
`sshd` nimmt seit 00:2x **nur noch Schluessel** an — `PasswordAuthentication no`
|
|
*und* `KbdInteractiveAuthentication no`, denn unter Windows laeuft die
|
|
Passwortabfrage sonst ueber den zweiten Weg weiter. Die Zeilen stehen **vor**
|
|
dem ersten `Match`-Block; weiter unten waeren sie stillschweigend wirkungslos
|
|
gewesen.
|
|
|
|
```
|
|
Darkster@127.0.0.1: Permission denied (publickey).
|
|
```
|
|
|
|
RDP ist per Firewall gesperrt (TCP und UDP, alle Profile). Der Zugang geht
|
|
durch einen SSH-Tunnel; `tools/tunnel-einrichten.ps1` richtet ihn auf dem
|
|
Heimrechner als Hintergrundaufgabe ein — ohne Fenster, mit Neuaufbau bei
|
|
Abbruch. **Was niemand durchhaelt, wird umgangen, und am Ende steht wieder ein
|
|
offener Port.**
|
|
|
|
Beides gemessen, und zwar von der richtigen Seite:
|
|
|
|
```
|
|
auf dem Server 127.0.0.1:3389 erreichbar (der Tunnel traegt)
|
|
::1 Typ 7, Darkster (RDP kam durch)
|
|
|
|
vom Heimrechner Test-NetConnection 88.218.224.10 -Port 3389
|
|
TcpTestSucceeded : False (von aussen zu)
|
|
```
|
|
|
|
Die Messung vom Server aus taugte dafuer **nicht** — Verkehr an die eigene
|
|
oeffentliche Adresse durchlaeuft die Eingangsregeln nicht und meldete
|
|
faelschlich "erreichbar". Zweimal in dieser Nacht in dieselbe Falle getappt,
|
|
einmal bei Port 3000 und einmal bei 3389.
|
|
|
|
Seitdem laufen die Angriffe ins Leere:
|
|
|
|
```
|
|
00:53:12 Connection closed by invalid user root 31.77.227.120 [preauth]
|
|
alle 12 erfolgreichen Anmeldungen: eine IP, die des Betreibers
|
|
```
|