Files
d4rk_media/docs/vorfall-2026-08-12-gitea.md
T
D4rkst3randClaude Opus 5 8e3a13b374 docs: RDP ist zu -- von aussen gemessen, nicht angenommen
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>
2026-08-13 01:09:06 +02:00

7.0 KiB

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