Ein AWS-Zugriffsschlüssel in der Git-Historie

Ein vor einem Jahr gelöschter Schlüssel lag noch in der Git-Historie und öffnete das gesamte S3 der Firma. Rotation, engere Rechte, Prüfung in der CI.

Firma
Softwarestudio, 9 Personen, ein Buchungssystem für Fitnessstudios
Team
Drei Entwickler, niemand ist für Infrastruktur oder Sicherheit zuständig
Umgebung
Anwendung auf einem VPS außerhalb von AWS, Anhänge und Backups in Amazon S3
Entwicklung
Privates GitHub-Repository, Auslieferung über GitHub Actions

01Ausgangslage

Die Anwendung läuft auf einem VPS bei einem anderen Anbieter, kann also keine IAM-Rolle bekommen wie ein Server in AWS, und meldet sich bei S3 mit dem langlebigen Zugriffsschlüssel des IAM-Benutzers app-uploads an. Dieser hatte die verwaltete Richtlinie AmazonS3FullAccess, also vollen Zugriff auf alle Buckets im Konto, auch auf den mit den Datenbank-Backups.

  1. Entwickler
  2. GitHub (privates Repository)
  3. GitHub Actions
  4. VPS: Anwendung
  5. Amazon S3: Anhänge und Backups

02Das Problem

Bei der Übergabe des Repositorys an einen neuen externen Entwickler wurde die gesamte Historie mit gitleaks geprüft (gitleaks git -v .). Es fand einen AWS-Zugriffsschlüssel in einer .env-Datei, die vor über einem Jahr versehentlich committet und zwei Tage später durch einen weiteren Commit gelöscht worden war. Das Löschen aus der Datei entfernte ihn nicht aus der Historie. Wer einen Klon hatte, hatte ihn auf der Platte.

Der Schlüssel war noch aktiv, die Produktion nutzte ihn.

Wer ihn haben konnte: aktuelle und ehemalige Entwickler, zwei Freelancer, deren Laptops und jeder Dienst mit Zugriff auf das Repository. Mit ihm ließen sich Kundendateien und Datenbank-Backups lesen, überschreiben und löschen.

Annahme: Das Repository war nie öffentlich. Wäre es das gewesen, müsste man davon ausgehen, dass ein Fremder den Schlüssel schon hat, denn öffentliche Repositorys werden von Bots gezielt nach solchen Schlüsseln durchsucht.

03Technische Ursache

Das Geheimnis stand in einer Datei, die nicht in .gitignore war, und nichts prüfte Commits vor dem Push. Git ist dafür gebaut, Historie zu bewahren. Eine Datei in einem späteren Commit zu löschen, entfernt aus den früheren nichts.

Eine zweite Ursache vergrößerte die Auswirkung: Die Anwendung musste Anhänge in einem Bucket hochladen und lesen, der Schlüssel hatte aber Rechte auf das gesamte S3. Ein geleakter Schlüssel bedeutete damit auch Zugriff auf die Backups.

04Untersuchung

Die IAM-Konsole zeigt zu jedem Schlüssel, wann, in welcher Region und für welchen Dienst er zuletzt verwendet wurde. Die letzte Nutzung passte zur Produktionsanwendung.

CloudTrail bewahrt Verwaltungsereignisse (management events), etwa das Auflisten von Buckets oder Rechteänderungen, 90 Tage lang ohne jede Einrichtung auf, und der Ereignisverlauf lässt sich nach Zugriffsschlüssel filtern. Aufrufe von anderen IP-Adressen als dem Produktionsserver gab es nicht.

Lesen und Schreiben einzelner S3-Objekte sind Datenereignisse (data events), die CloudTrail standardmäßig nicht aufzeichnet, und sie waren nicht aktiviert. Dass niemand Dateien heruntergeladen hat, lässt sich daher nicht belegen. Genau so steht es im Vorfallsprotokoll, nicht als Schluss, es sei nichts abgeflossen.

05Behebung

1. Rotation ohne Ausfall: neuen Schlüssel anlegen, auf dem Server einspielen, prüfen, dass die Anwendung läuft, den alten deaktivieren und nach einigen Tagen ohne Fehler löschen. Ein deaktivierter Schlüssel lässt sich bei Problemen wieder einschalten, ein gelöschter nicht.

2. Der neue Schlüssel gehört einem Benutzer, dessen Richtlinie nur erlaubt, was die Anwendung tut: ein Objekt unter einem Präfix eines Buckets hochladen und lesen. Auf die Backups hat er keinen Zugriff.

IAM policy: app-uploads
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:PutObject", "s3:GetObject"],
      "Resource": "arn:aws:s3:::rezervacie-prilohy/uploads/*"
    }
  ]
}

3. Geheimnisse raus aus dem Repository: .env steht in .gitignore, im Repository bleibt nur eine .env.example mit leeren Werten. Auf dem Server ist die Datei nur für den Benutzer lesbar, unter dem die Anwendung läuft. Geheimnisse für die Auslieferung liegen in den GitHub Actions secrets.

4. Prüfung vor und nach dem Push: gitleaks läuft als Pre-Commit-Hook bei den Entwicklern und noch einmal in der CI bei jedem Pull Request, wo es sich nicht durch Abschalten des Hooks umgehen lässt. Der alte, bereits ungültige Schlüssel ist über den Fingerabdruck des Befunds in .gitleaksignore eingetragen, sonst würde die Prüfung der Historie in der CI dauerhaft fehlschlagen.

5. Historie: Nach der Rotation ist der Schlüssel in der Historie ungültig und damit harmlos. Die Historie umzuschreiben (etwa mit git filter-repo) wäre möglich, aber alle müssten neu klonen, und bereits vorhandene Kopien blieben unberührt. Die Firma hat die Historie nicht umgeschrieben und verlässt sich auf die Rotation.

Erwogene Alternativen: In AWS bräuchte die Anwendung gar keinen langlebigen Schlüssel und bekäme kurzlebige Anmeldedaten über eine IAM-Rolle. Außerhalb von AWS gibt es IAM Roles Anywhere, das allerdings eine eigene Zertifizierungsstelle voraussetzt, für dieses Team unnötig aufwendig. Das Blockieren von Geheimnissen beim Push (push protection) ist für private Repositorys eine kostenpflichtige GitHub-Funktion.

06Warum die Lösung wirkt

Die Rotation macht den geleakten Schlüssel wertlos: AWS lehnt einen deaktivierten oder gelöschten Schlüssel ab, wo auch immer er gespeichert ist. Deshalb ist die Rotation die eigentliche Maßnahme, das Umschreiben der Historie wäre Kosmetik.

Die engere Richtlinie begrenzt die Auswirkung eines künftigen Lecks. Ein Schlüssel, der nur s3:PutObject und s3:GetObject unter einem Präfix darf, kann weder Buckets auflisten noch löschen noch Backups lesen.

gitleaks sucht in Änderungen nach Mustern bekannter Geheimnistypen. Der Hook fängt einen Fehler ab, bevor er den Rechner verlässt, die CI fängt den Fall ab, dass jemand den Hook nicht hat oder umgeht.

Was das nicht löst: Der Schlüssel auf dem Server ist weiterhin langlebig, und wer den Server übernimmt, bekommt ihn. Die Erkennung beruht auf Mustern, ein Geheimnis in ungewöhnlichem Format kann durchrutschen.

07Überprüfung

  • Ein Aufruf von aws sts get-caller-identity mit dem alten Schlüssel endete mit InvalidClientTokenId.
  • Mit dem neuen Schlüssel funktionieren Hochladen und Lesen von Anhängen, während aws s3 ls und das Lesen aus dem Backup-Bucket mit AccessDenied enden.
  • Ein Test-Commit mit einem gefälschten Schlüssel wurde vom Pre-Commit-Hook und von der CI-Prüfung gestoppt.
  • Ein neuer gitleaks-Lauf über die gesamte Historie fand kein weiteres Geheimnis.

08Ergebnisse

Behoben

  • Der geleakte Schlüssel ist ungültig und gelöscht.
  • Geheimnisse sind aus dem Repository, neue gelangen nicht ohne Warnung hinein.

Risiko gesenkt

  • Die Auswirkung eines Lecks des Anwendungsschlüssels schrumpfte vom gesamten S3 auf Anhänge unter einem Präfix.

Weiter offen

  • Es lässt sich nicht belegen, dass in diesem Jahr niemand Dateien aus S3 heruntergeladen hat.
  • Die Anwendung nutzt weiterhin einen langlebigen Schlüssel.

09Grenzen

  • Die Geheimniserkennung ist nicht vollständig: Sie erkennt bekannte Formate, nicht jedes Passwort.
  • Sie erreicht keine Kopien des Repositorys, die bereits außerhalb der Firma existieren.
  • Ohne CloudTrail-Datenereignisse wüsste die Firma auch bei einem künftigen Leck nicht, welche Dateien gelesen wurden.

10Erkenntnisse

  • Aus einer Datei löschen heißt nicht aus Git löschen. Ein geleaktes Geheimnis wird durch Rotation erledigt.
  • Ein Anwendungsschlüssel darf genau das, was die Anwendung tut, nicht mehr.
  • Backups dürfen mit dem Schlüssel, mit dem die Anwendung arbeitet, nicht erreichbar sein.
  • Die Prüfung in der CI ist Pflicht, der Hook ist Komfort für den Entwickler.
  • Ein Vorfallsprotokoll sagt, was sich belegen lässt und was nicht.

11Nächste Schritte für eine kleine Firma

  1. 1.CloudTrail-Datenereignisse mindestens für den Backup-Bucket einschalten (sie kosten, also gezielt).
  2. 2.Zwei-Faktor-Authentifizierung für alle mit Zugang zur AWS-Konsole und zu GitHub einschalten.
  3. 3.Beim nächsten Hostingwechsel den Betrieb in AWS oder IAM Roles Anywhere erwägen, damit der langlebige Schlüssel wegfällt.
  4. 4.Einmal im Quartal IAM-Benutzer und Schlüssel durchgehen und ungenutzte löschen.
Kontakt aufnehmen

Erkennen Sie Ihre eigene Firma darin?

Schreiben Sie uns, worum es geht. Wir antworten innerhalb eines Werktags und sagen Ihnen, ob es Arbeit für uns ist, auch wenn die Antwort nein lautet.