Notfall-Wiederherstellung mithilfe der GitHub Events API » serra.me

Notfall-Wiederherstellung mithilfe der GitHub Events API

Am 16. August 2020 haben Unbekannte das libretro-Projekt angegriffen. Zuerst sich der Angreifer Zugriff auf den Buildbot-Server des Projektes und löschte diesen nahezu vollständig. Anschließend gelang es ihm, das GitHub-Profil eines hochrangigen Mitglieds des libretro-Teams zu übernehmen. Mithilfe dieses Accounts zerstörte er daraufhin durch das pushen eines leeren, „initialen“ Commits mehrere Repositories.

Solche Attacken sind nicht unüblich und in der Vergangenheit auch bereits mehrfach vorgekommen. Auf den ersten Blick erscheint es, dass in diesem Fall alle Daten der betroffenen Repositories verloren sind.

Git löscht üblicherweise keine Dateien!

Tatsächlich sorgt das force-pushing eines „leeren“ Commits nur dafür, dass die History des Repositories zurückgesetzt wird und auch alle Referenzen zu den vorherigen Commits verworfen werden. Solange du (oder in diesem Fall GitHub) nicht den Garbage Collector von Git ausführen, sind deine Daten in Wirklichkeit noch vorhanden.

Um dein Repository wiederherzustellen, benötigen wir lediglich den Commit-Hash des letzten legitimen Commits. Dies gilt selbst dann, wenn wir aufgrund fehlender oder defekter Backups auf einen frischen Klon des Repositories zurückgreifen müssen.

Glücklicherweise können wir den gesuchten Commit-Hash über die GitHub Events API abfragen. Für dieses Beispiel schauen wir uns einmal das Repository libretro/libretro-samples an, erstmalig einige Stunden nach dem Vorfall geklont. Zu diesem Zeitpunkt sieht die Git-History des Repositories folgendermaßen aus:

# git log
commit daf240eecb37aaa15e2ad4c499bb9fb39132ddc9 (HEAD -> master, origin/master, origin/HEAD)
Author: Your Name <you@example.com>
Date:   Sun Aug 16 11:58:28 2020 +0800

    initial

Alles, was in dem Repository noch vorhanden ist, ist eine leere Datei mit dem Namen README.md.

Jeder Operation in einem GitHub-Repository löst ein Event aus, auf welches wir über die API zugreifen können. In diesem Fall benötigen wir die Events vom Typ PushEvents. Mit curl und jq können wir die Daten des „bösartigen“ PushEvents abfragen:

curl https://api.github.com/repos/libretro/libretro-samples/events | jq '.[] | select(.payload.commits[]?.sha=="daf240eecb37aaa15e2ad4c499bb9fb39132ddc9")' | jq '.payload'

Als Ergebnis erhalten wir folgende Ausgabe:

{
  "push_id": 5535666635,
  "size": 1,
  "distinct_size": 1,
  "ref": "refs/heads/master",
  "head": "daf240eecb37aaa15e2ad4c499bb9fb39132ddc9",
  "before": "094d8d807da9dff5a0ec5ab4958cb68eb8c275ce",
  "commits": [
    {
      "sha": "daf240eecb37aaa15e2ad4c499bb9fb39132ddc9",
      "author": {
        "email": "you@example.com",
        "name": "Your Name"
      },
      "message": "initial",
      "distinct": true,
      "url": "https://api.github.com/repos/libretro/libretro-samples/commits/daf240eecb37aaa15e2ad4c499bb9fb39132ddc9"
    }
  ]
}

Der Commit unmittelbar vor dem Vorfall wird in dem Schlüssel before gespeichert, was bedeutet, dass in diesem Fall 094d8d807da9dff5a0ec5ab4958cb68eb8c275ce den letzten legitimen Commit darstellt.

Um zu dem letzten „guten“ Commit zurückzukehren und den Inhalt wiederherzustellen, müssen wir den Commit explizit abrufen und anschließend einen Reset des Repositories auf diesen Commit erzwingen:

git fetch origin 094d8d807da9dff5a0ec5ab4958cb68eb8c275ce
git reset --hard 094d8d807da9dff5a0ec5ab4958cb68eb8c275ce

Jetzt fürfen wir die History des Repositories noch einmal – alle Commits sind wieder in der History vorhanden und damit auch dein Code. Abschließend bleibt jetzt nur noch, den aktuellen Stand zurück in das Repository zu (force-) pushen.

Fazit

Sofern du in der Lage bist, den Angriff selbst zeitnah abzuwehren und den unbefugten Zugriff zu unterbinden, stehen die Chancen gut, dass du auch in diesem Szenario deine Repositories wiederherstellen kann. Dieser Prozess verlässt sich jedoch darauf, dass Garbage Collection von Git nicht ausgeführt wurde und der letzte bekannte Commit-Hash in Erfahrung gebracht werden kann.

Diese Garbage Collection führt einige Wartungsaufgaben durch. Unter anderem werden dabei auch (tatsächlich) obsolete Daten und verwaiste Commits gelöscht. Wir wissen nicht, wie häufig GitHub die Garbage Collection auf dort gehostete Repositories anwendet. Die GitHub-API selbst speichert die Daten laut eigenen Angaben für 90 Tage, weshalb ich annehme, dass zumindest während diesem Zeitfenster von 90 Tagen eine Wiederherstellung möglich ist.

Wenn du deinen eigenen Git-Server betreibst, wirst du keinen Zugriff auf eine entsprechende API haben. In diesem Fall bleibt nur, die Commit-Hashes separat zu speichern bzw. aufzuzeichnen. Geht der Zugriff auf die Commit-Hashes verloren, ist keine einfache Wiederherstellung mehr möglich.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.