Blog

Manipulationsangriffe abwehren – eure Software Delivery Chain absichern

MAR 31, 2021

Nach dem SolarWinds-Angriff hat sich die Kompromittierung von Source Code als praktikable und sehr wirksame Methode für Cyberangriffe erwiesen. Dabei nutzt der Angreifer das Vertrauen in das kompromittierte Unternehmen, um seinen Zugriff auf die Kernnetzwerke mehrerer Kunden auszuweiten.

Sofus Albertsen

Sofus is a Continuous Delivery Trainer and Consultant in Copenhagen. Before joining Eficode Praqma he was assistant professor on an Applied Science Bachelor program. He flies kites and in the summer he escapes the modern world by spending two weeks in a beach hut without electricity or network coverage.

Darüber hinaus wurde gestern bekannt, dass auch das PHP-Source-Code-Repository kompromittiert wurde. Wäre dies unbemerkt geblieben, hätten alle Server mit PHP angegriffen werden können, sobald sie die PHP-Version aktualisierten. Zur Klarstellung: Die kompromittierten Commits wurden entdeckt, bevor sie die Produktionsversion von PHP erreichten – ihr müsst euch also keine Sorgen machen, wenn ihr PHP-Server betreibt. Der Vorfall zeigt jedoch deutlich, wie wichtig Sicherheit in eurer Software-Lieferkette ist.

Tatsächlich zeigen beide Geschichten, dass Source-Code-Leaks möglicherweise nicht länger unser vorrangiges Sicherheitsrisiko bei Sicherheitsverletzungen sind.

Sicherheit ist ein sehr komplexes Thema. Dieses Blog hat nicht das Ziel, Everything™ zu lösen, sondern konzentriert sich auf die Risiken in unserer Software-Lieferkette und zeigt Beispiele, wie ihr ihnen entgegenwirken könnt.

Eure Lieferkette mit der CIA-Triade absichern

Die CIA-Triade kann bei der Risikobewertung eingesetzt werden. Sie besteht aus drei Elementen:

  • Vertraulichkeit – Schutz und Geheimhaltung von Informationen. Dies gilt für Softwarealgorithmen, Geschäftsprozesse und Endnutzerdaten. Verlorene Geheimnisse können Wettbewerbsvorteile beeinträchtigen, das Vertrauen der Endnutzer mindern oder sogar rechtliche Schritte nach sich ziehen, etwa aufgrund der DSGVO.

  • Integrität – Schutz unserer Informationen vor Manipulation. Dies gilt sowohl für Softwareartefakte als auch für Daten, einschließlich Endnutzerdaten. Verlorene Integrität kann dazu führen, dass unsere Softwareplattform für Hacker offensteht und dadurch auch ein weitreichender Verlust der Vertraulichkeit entsteht.

  • Verfügbarkeit – die Verfügbarkeit unserer Softwareservices. Dies betrifft die Gestaltung unserer Infrastruktur und Anwendungen sowie ihre Widerstandsfähigkeit gegenüber böswilligen Angriffen. Es wirkt sich auch auf unsere Prozesse aus – nutzen wir beispielsweise solide DevOps-Prozesse, die bei Ausfällen eine kurze Wiederherstellungszeit gewährleisten? Ausfälle können durch uns selbst verursacht werden, etwa durch Softwareupdates, oder durch externe Ereignisse, etwa bei der Disaster Recovery.

Die Software-Lieferkette schützen

Um diesen Leitfaden greifbar zu machen, veranschaulichen wir den Ablauf vom Entwickler-Commit bis zum Artefakt in der Produktion.

In dieser Situation erstellt der Entwickler einen Commit und pusht ihn in das Git-Repository. Anschließend wird die CI/CD-Pipeline ausgelöst und stellt eine neue Version des Artefakts in der Produktion bereit.

Git signing drawings

Im obigen Beispiel wird die Datenübertragung durch SSH-Tunnel abgesichert. Ein böswilliger Angreifer kann jedoch weiterhin Commits im Git-Repository platzieren, wenn er Zugriff auf das Repository hat, ohne entdeckt zu werden.

Eine Sicherheitsebene in unsere Software-Lieferkette einfügen

Unsere Arbeitsweise mit Git wirkt sich auf die Vertraulichkeit und Integrität unserer Software-Lieferkette aus. Entwickler nutzen in der Regel Folgendes, um Git-Operationen abzusichern:

  • Ein SSH-Schlüssel wird verwendet, um 1) unsere Verbindung zum Git-Server über einen SSH-Tunnel zu verschlüsseln und 2) uns gegenüber dem Git-Server zu authentifizieren und unsere Aktionen zu autorisieren.

  • HTTPS für eine GitWeb-UI. Obwohl das in HTTPS verwendete TLS-Protokoll sicher ist, verfügen Unternehmens-PCs häufig über eigene Stammzertifizierungsstellen. Das heißt, die HTTPS-Verschlüsselung zum Schutz der Git-UI ist möglicherweise nicht Ende-zu-Ende verschlüsselt, sondern wird innerhalb des Unternehmens abgefangen.

Wir können daraus schließen, dass die Vertraulichkeit geschützt ist, wenn wir über das SSH-Protokoll mit unserem Git-Repository kommunizieren. SSH-Tunnel bieten jedoch nur lokale Vertraulichkeit, da sie nicht Ende-zu-Ende, sondern nur Punkt-zu-Punkt innerhalb der Lieferkette reichen. Die Integrität unseres Source Codes ist hingegen nicht geschützt. Dafür ist ein zusätzlicher Schritt erforderlich: die kryptografische Signierung eurer Commits.

GPG-Schlüssel werden verwendet, um Git-Commits und -Tags zu signieren. Ein signiertes Git-Objekt bestätigt, dass der Commit oder Tag von jemandem erstellt wurde, der Zugriff auf den privaten Signaturschlüssel hat – und gewährleistet damit die Integrität jedes einzelnen Commits im Repository.

Um GPG mit GitHub zu verwenden, erstellt einen GPG-Schlüssel und verknüpft ihn mit eurem Konto. Wie das geht, erfahrt ihr in der Dokumentation auf github.com. Auch GitLab und Bitbucket Server/DC unterstützen die Verwendung von GPG-Schlüsseln. Bitbucket Cloud leider nicht.

Euer Git-Client unterstützt bereits die Verwendung von GPG. Wie das funktioniert, könnt ihr auf der Startseite des Git-Clients nachlesen.

Nachdem ihr euren signierten GPG-Schlüssel erstellt habt, könnt ihr Git so konfigurieren, dass alle folgenden Commits und Tags automatisch signiert werden, indem ihr euren Schlüssel in der Git-Konfiguration hinterlegt.

$git config --global user.signingkey 0A46826A

Git verwendet nun standardmäßig euren Schlüssel, um Tags und Commits zu signieren, wenn ihr das möchtet.

Hinweis: HTTPS kann nicht verwendet werden, um die Integrität zu schützen. Über die UI können wir die meisten Git-Operationen ausführen, jedoch keine Commits signieren, da sie keinen Zugriff auf unseren privaten Signaturschlüssel hat.

Die neue abgesicherte Software-Lieferkette

In diesem Abschnitt stellen wir den Workflow für einen Entwickler vor, der eine CI-Pipeline nutzt, um Container-Images für den Einsatz in einer Produktionsumgebung zu erstellen. Dabei gehen wir davon aus, dass der Entwickler Commits signiert.

Dieser Workflow veranschaulicht auch die Verwendung von SSH-Schlüsseln für den Zugriff auf Git. Das heißt, wir sehen SSH-Tunnel zwischen dem Entwickler und Git sowie zwischen Git und der Pipeline. Git-Signaturen gewährleisten eine starke End-to-End-Integrität. Das ist wichtig, da ein Verlust der Integrität auch zu einem Verlust der Vertraulichkeit führen kann. Git-SSH-Schlüssel für den Zugriff auf Git bieten nur lokale Vertraulichkeit.

Git signing drawings (1)

Die CI-Pipeline erstellt Container-Images aus dem Git-Quellcode und sollte Git-Signaturen validieren, um Vertrauen zwischen Entwickler und Pipeline herzustellen. Die CI-Pipeline muss zudem das erstellte Container-Image signieren, um über die Container-Image-Registry hinweg die Integrität zwischen Pipeline und Produktionsumgebung sicherzustellen (siehe Docker Content Trust). Unser CI/CD-System ist daher eine Vertrauensbrücke zwischen Quellcode und Binärartefakt.

In diesem Workflow kann die CI-Pipeline eine Schwachstelle sein, da sie komplexe Vorgänge unter Kontrolle der Quellanwendung und mit einer potenziell großen Vielfalt an Tools ausführt. Dadurch hat die CI-Pipeline eine große Angriffsfläche. Diese Schwäche wird dadurch verstärkt, dass die CI-Pipeline Vertrauen von den vom Entwickler signierten Commits auf für die Produktion validierte signierte Images überträgt. Wird diese Vertrauensbrücke kompromittiert – etwa indem die CI-Pipeline dazu gebracht wird, aus nicht ordnungsgemäß signiertem Quellcode zu erstellen –, geht die End-to-End-Integrität verloren.

Um dies zu vermeiden, sollten wir einen externen Agenten einsetzen, der Git-Signaturen zusätzlich überprüft. Diese Prüfung sollte von einem Agenten durchgeführt werden, der nicht direkt mit dem CI-System verbunden ist oder von ihm beeinflusst wird. Wir gehen also davon aus, dass es sehr unwahrscheinlich ist, dass er gleichzeitig mit dem CI-System kompromittiert wird.

Die Architektur des externen Signaturprüfers ist unten dargestellt:

Git signing drawings (2)

So überprüft ihr eure Commits

Michael hat einen Git-Signaturprüfer entwickelt, der sowohl in CI-Pipelines als auch als externer Git-Signaturprüfer eingesetzt werden kann. Ihr findet ihn hier; er ist Open Source.

Der Prüfer benötigt ein Verzeichnis mit öffentlichen Schlüsseln für alle Signaturschlüssel, denen vertraut werden soll. Ihr könnt euren öffentlichen Git-GPG-Schlüssel wie folgt exportieren. Beachtet jedoch, dass ihr alle öffentlichen Schlüssel benötigt, die als gültige Signaturschlüssel gelten:

$ gpg --armor --export user@example.com > user-pub-key.asc

Führt anschließend den Signaturprüfer mit einem Git-Repository und eurer Liste vertrauenswürdiger öffentlicher Schlüssel wie folgt aus:

$ docker run --rm -v $PWD/public-keys:/public-keys:ro -v $PWD/.git:/repo:ro michaelvl/git-signature-checker

Alternativ könnt ihr einen vorhandenen GPG-Schlüsselbund verwenden, dem möglicherweise bereits Vertrauen für Schlüssel zugewiesen wurde:

$ docker run --rm -v $HOME/.gnupg:/gnupghome:ro -v $PWD/.git:/repo:ro michaelvl/git-signature-checker --git-dir /repo --keyring /gnupghome --minimum-trust ULTIMATE

Standardmäßig validiert der Prüfer alle Commits der aktuellen Revision des angegebenen Repositorys. Verwendet das Argument --revision-range, um alternative Referenzen und Teilbäume zu prüfen.

Hinweis: Zum Zeitpunkt der Veröffentlichung zeigt GitHub die Commits des Repositorys als nicht verifiziert an. Das liegt daran, dass der zum Signieren der Commits verwendete Schlüssel abgelaufen ist. Unbefristete Schlüssel sind aus Sicherheitssicht riskant, doch Schlüssel mit zu kurzer Gültigkeit können dazu führen, dass gültig signierten Commits nicht mehr vertraut wird.

Screenshot from 2021-03-30 11-07-43

Eine ähnliche Situation entsteht, wenn Mitarbeiter das Unternehmen verlassen: Ihr solltet ihren Signaturschlüssel widerrufen, den von ihnen in der Vergangenheit erstellten Signaturen sollte aber weiterhin vertraut werden. Bewertet die Gültigkeit einer Signatur grundsätzlich anhand des Status des Schlüssels zum Zeitpunkt der Signatur.

Hinweis zu öffentlichen Schlüsseln

Dieses Tool validiert nicht die öffentlichen Schlüssel, die über den mit --public-keys angegebenen Pfad importiert werden. Das Vertrauen zwischen Schlüsseln und die erforderliche Vertrauensstufe lassen sich definieren, indem ihr mit --keyring einen bestimmten GPG-Schlüsselbund mit Schlüsseln und festgelegtem Vertrauen verwendet. Mit --minimum-trust könnt ihr die Mindestvertrauensstufe für Schlüssel angeben, die für eine erfolgreiche Signaturvalidierung erforderlich ist.

Eine sorgfältige Schlüsselverwaltung ist selbstverständlich Voraussetzung dafür, die Git-Integrität mit Signaturen zu validieren.

Hinweise zu GitHub-Merges über die Web UI

Wenn ihr die GitHub Web UI nutzt, um PRs zusammenzuführen, werden eure Merges mit einem öffentlichen GitHub-Schlüssel und nicht mit eurem eigenen Schlüssel signiert. Dadurch wird das End-to-End-Vertrauen unterbrochen, denn wir können nicht wissen, wer auf die UI zugegriffen hat – möglicherweise jemand mit einem kompromittierten Benutzernamen/Passwort. Daher solltet ihr in der Web UI immer 2FA verwenden. 

Ihr könnt PRs auch selbst zusammenführen. In diesem Fall müsst ihr den öffentlichen GitHub-Schlüssel in die Liste der vertrauenswürdigen Signaturschlüssel aufnehmen. Den Schlüssel findet ihr hier:

curl https://github.com/web-flow.gpg

Dieses Problem betrifft auch die anderen Git-Anbieter, die im Abschnitt „Eine Sicherheitsebene in unserer Software-Delivery-Chain hinzufügen“ genannt werden.

Hinweis: Wenn ihr die Schlüssel des Servers als vertrauenswürdig importiert, könnt ihr euren Git-Repository-Server nicht mehr als kompromisssicher betrachten. Die Nachvollziehbarkeit von Merges geht verloren, und über die UI des Servers hinzugefügte Commits werden als vertrauenswürdig eingestuft.

Wenn ihr nur Fast-Forward-Merges zulasst, könnt ihr das Beste aus beiden Welten erhalten, da der Git-Server weder Commits abwechselt noch hinzufügt. Dafür müsst ihr eure PRs stets auf dem aktuellen Stand von master halten.

Best Practice

Wir empfehlen, eure GPG-Zertifikate auf einem Hardware-Token zu speichern. So könnt ihr das Risiko verringern, dass euer Schlüssel gestohlen wird – selbst wenn euer Computer kompromittiert wird. Ein solcher Schlüssel ist der YubiKey.

Auf einem YubiKey gespeicherte Schlüssel können nicht exportiert werden – anders als dateibasierte Schlüssel, die auf einem Datenträger gespeichert sind – und sind bequem im Arbeitsalltag nutzbar. Statt euch Passphrasen merken und eingeben zu müssen, um SSH-/GPG-Schlüssel zu entsperren, benötigt der YubiKey nach der Entsperrung mit einer PIN nur eine physische Berührung. Alle Signatur- und Verschlüsselungsvorgänge finden auf der Karte statt, nicht im Speicher des Betriebssystems.

Eine ausführlichere Anleitung findet ihr in diesem GitHub-Repository, dessen Beschreibungen wir gut geschrieben finden. Bitte lest sie sorgfältig, da wir die bereitgestellten Informationen keinem Sicherheitsaudit unterzogen haben.

Abschließende Hinweise

Wenn ihr GitOps und Kubernetes nutzt, gibt es mehrere Möglichkeiten, die Integritätsprüfung in die Infrastruktur zu integrieren.

Wenn beispielsweise das SRE-Team alle Commits signiert, kann der GitOps-Agent Git-Commit-Signaturen prüfen, bevor Änderungen auf Kubernetes angewendet werden. Das gewährleistet durchgängige Integrität bei der Bereitstellung von Infrastrukturänderungen. 

Zur Validierung von Images könnten wir reproduzierbare Builds verwenden. In der Praxis ist das jedoch recht kompliziert und bringt eigene Probleme mit sich. Beispielsweise darf die Pipeline für den sekundären Build nicht denselben Angriffen ausgesetzt sein wie die primäre Pipeline.

Sichere Nachvollziehbarkeit in eurer Software-Delivery-Chain

Bei Sicherheitsvorfällen konzentrieren wir uns häufig auf den Diebstahl von geistigem Eigentum in Form von Source Code. Stattdessen müssen wir unseren Fokus darauf richten, die Nachvollziehbarkeit in unserer Software-Delivery-Chain zu sichern, um Angriffe wie auf SolarWinds zu verhindern.

Das Signieren und Überprüfen jedes Commits verringert das Risiko, dass ein böswilliger Angreifer unbemerkt Exploits in eure Software einschleust und damit die Integrität eurer Software gefährdet.

Um dies richtig umzusetzen, braucht es neue Tools und die Gewissheit, dass alle mit deren Nutzung vertraut sind. Die Alternative hat sich als so verheerend erwiesen, dass wir uns fragen müssen, ob wir bereit sind, dieses Risiko einzugehen.

  • CI/CD
  • Sicherheit

Subscribe to our newsletter