Secure Coding / Secure Development
Zunächst möchte ich etwas betrachten, das bereits in der Vergangenheit durch Ken Thompson nachgewiesen wurde. Seine Idee war, nicht direkt ein Programm, in diesem Fall UNIX, sondern den Compiler des Sourcecodes zu kompromittieren. (1)(2)
Beim Kompilieren des Passwortmoduls hat der Compiler festgestellt, dass er nun genau dieses kompiliert und hat Zeilen „hinzugefügt“. Dadurch wurden in allen Versionen, die mit diesem Compiler gebaut wurden, das Passwort „ken_thompson“ ebenfalls akzeptiert, zusammen mit dem eigentlichen Passwort. Dies passierte im Jahr 1984 und war zu diesem Zeitpunkt der Beweis, dass nicht nur der Code, sondern auch die Werkzeuge, mit denen er erzeugt und kompiliert wird, sicher sein müssen.
Repositories
Im Dezember 2020 wurde die Firma Solarwinds durch Hacker angegriffen. Dabei wurde die Buildpipeline des Unternehmens, besonders für die Produkte Orion (eine Assetmonitoring Plattform) und der Serv-U FTP Server (ein Datei-Server) kompromittiert. (3) Ich möchte an dieser Stelle weniger auf die Datenleaks bzw. die Auswirkungen des Ganzen eingehen, sondern vielmehr betrachten, was technisch passierte. Dies betrifft im Wesentlichen die Punkte „Repositories“, „Buildpipeline“ und „Zertifizierungskette“.
Die „Hacker“ (ich nutze es beabsichtigt in Anführungszeichen, da es sich per Definition eher um Cracker handelt) haben sich bei Solarwinds zunächst Zugriff auf die Quellcodes von zumindest den oben genannten Produkten Orion und ServU verschafft. Dabei haben sie Einblicke auf die Funktionsweise und die Struktur sowie mögliche Schwachpunkte dieser Produkte erhalten, die für weitere Hacks durchaus zum Einsatz kommen könnten.
Dies ist für mich der erste kritische Punkt. Denn Software ist selten fehlerfrei. In der Regel steigt mit der Komplexität eines Softwareprodukts auch die Fehlerzahl. In den letzten Jahren ist durch die Nutzung von Frameworks, wie wiederum andere Frameworks nutzen, die Komplexität neuer Software stark gestiegen. Hier möchte ich eines meiner Lieblingsbeispiele anbringen: Während die Software des Spaceshuttles „Columbia“ ca. 400.000 Zeilen Code hatte (ca. 1960), besteht ein kleiner Webshop aus dem Jahr 2021 aus über 4.000.000 Zeilen Code. Die eingesetzten Bibliotheken haben meist mehr Funktionalität als für den Einsatzzweck überhaupt notwendig. Diese bringen jedoch Schnittstellen oder „Features“ mit sich, die vom Entwickler nicht betrachtet werden, weil sie oftmals gar nicht genutzt werden. Es muss also bei der Bibliotheken-Nutzung nicht nur der eigene Code, sondern auch der von dritten betrachtet und - im Sinne von Secure Coding - untersucht werden.
Buildpipeline
Der zweite kritische Punkt ist für mich die „Buildpipeline“. Dabei ist nicht nur zum Beispiel ein Jenkins-Job zu betrachten, sondern alle Tools, Server und Scripte, wie zum Entwickeln und zum „Bauen“ der Software genutzt werden. Im Prinzip muss dies bis auf Betriebssystemebene betrachtet werden. In der Annahme, dass es auf dieser Ebene eine ausreichende Sicherung gibt, möchte ich mir lediglich die Tool-Landschaft anhand eines Beispiels ansehen:
Nehmen wir an, zur Entwicklung wird ein Visual Studio Code (VSC) mit Plugins für Java verwendet, Code wird per Git auf einem Server verwaltet und die Builds werden automatisch durch einen Jenkins-Server ausgeführt. Am Ende der Pipeline wird noch per Skript auf eine virtuelle Maschine (VM) ausgerollt, in meinem Falle ein Docker Container, und gestartet. So oder so ähnlich kann man dieses Setup auch bei diversen Firmen im Einsatz vorfinden. Es finden sich bereits die ersten Kompromittierungsvektoren im VSC. Der Code ist zwar OpenSource und für jeden einsehbar, gleichzeitig ist es aber für ein einzelnes Projekt eine kaum zu stemmende Aufgabe, dies komplett zu durchleuchten. Das gleiche „Problem“ stellt sich auch mit anderen Entwicklungsumgebungen (IDE) (wie Eclipse oder Netbeans), dabei sind für mich Closed Source Umgebungen noch problematischer, da man sich auf die Aussagen des Herstellers verlassen muss und nicht selbst prüfen kann. Das Problem mit eventuell kompromittierten IDEs wird durch die Nutzung von Plug-Ins noch verschärft. Ein Verzicht ist keine Lösung, da z. B. VSC ohne Plug-Ins nichts kann. Auch die Plug-Ins sind Open Source und bedeuten eine Menge zu prüfenden Code. Der entwickelte Code selbst würde per Git verwaltet werden. Auch ein Git kann falsch eingerichtet oder angegriffen werden. An dieser Stelle der kurze Hinweis, dass schon öfter Entwicklerteams sensible Daten auf einem öffentlichen Git Server (z. B. github) abgelegt haben, obwohl diese sogar explizit private Repositories anbieten. Aber auch ein selbst eingerichteter Git Server kann zum Ziel werden. Beispielsweise wenn der Server fehlerhaft eingerichtet oder auch hier das Tool selbst kompromittiert wurde. Eine Möglichkeit wäre ein „geknacktes“ Git, das entweder eine Backdoor öffnet oder den eingecheckten Code nach außen kopiert.
Die nächste Komponente in diesem Konstrukt ist der Jenkins-Server (auch hier gibt es natürlich andere Varianten). Sofern jemand auf diesen Server Zugriff hat, können u. a. Codeprüfungen deaktiviert, ein anderes Repository (mit „besseren Bibliotheken“), Passwörter eingesehen und Prozesse verändert werden. Zwar hat der Jenkins selbst eine History darüber, welche Jobs von wem konfiguriert wurden, jedoch nicht darüber, welche Einstellungen von wem durchgeführt wurden. Es ist zum Beispiel durch Anpassung der Jobs möglich, einen anderen Java Truststore zum Bauen mitzugeben, damit wäre ein großer Teil der gut gemeinten Security ausgehebelt.
Der letzte Teil meines Gedankenexperimentes sind die Skripte zum Ausbringen und Starten von gebauten Artefakten auf einer VM. Diese sehe ich am wenigsten kritisch, da sie im Normalfall übersichtlich gehalten sind und imperativen Code, also keine Klassen o. ä. enthalten. Jedoch können auch diese Skripte verändert werden und zum Beispiel für einen Rollout weitere Parameter mitgeben, oder weitere -ungewollte- Artefakte ausbringen.
Wie kann man sich nun gegen diese Bedrohungen schützen?
Auch wenn ich in den letzten Absätzen zunächst den Teufel an die Wand und ein Worst-Case Szenario gemalt habe: Es ist nicht alles verloren. Die IDEs und Plug-Ins sollten nur aus vertrauenswürdigen Quellen heruntergeladen werden, dabei sollten Hashwerte zum Vergleich des Paketes mit dem berechneten Wert vom Hersteller herangezogen werden.
Bei kritischen Projekten sollte zusätzlich eine Codeanalyse der Quellen der IDEs durchgeführt werden. Unangekündigte und dringende Updates müssen mit dem Herausgeber hinterfragt werden, denn ein als Update getarnter Hack ist nicht unüblich. Die Nutzung von Closed Source (im Sinne von Black Box, nicht im Sinne der Lizenz) Produkten kann ich an dieser Stelle nicht empfehlen, da man sich auf die Aussage des Herausgebers verlassen muss, ohne selbst prüfen zu können. Im Falle von Java sollte ebenfalls eher ein OpenJDK, als das „Original“ von Oracle verwendet werden. Auch die Pipeline kann bezüglich einer Kompromittierung resilient gestaltet werden. Eine Möglichkeit wäre es, über die eingerichteten Jobs bzw. die hinterlegte Konfiguration ebenfalls Hashwerte zu bilden, die regelmäßig überprüft werden. Bei einer Abweichung muss entsprechend geprüft werden, warum diese aufgetreten ist. Dieselbe Herangehensweise kann für Automatisierungsskripte genutzt werden: Zunächst eine Prüfung auf deren Integrität, anschließend Ausführung.
Und warum ist das gerade jetzt so wichtig?
Die wachsende Komplexität, bereits von einfachen Anwendungen, zeigt, dass diverse Vektoren existieren, über die Kompromittierungen auf Build-Prozesse realistisch sind.
Ich möchte mit dem Fazit schließen, dass eine einhundertprozentige Sicherheit kaum erreichbar ist. Software-Build-Prozesse können jedoch zumindest so gestaltet werden, dass Kompromittierungen erkannt werden können und ein Rollout von einer beschädigten Software verhindert werden kann.
Ein Tipp zum Schluss: Gebt auf eure Buildpipeline acht!
- Statische und Dynamische Codeanalyse (z. B. Sonarqube)
- Bereitstellung von Buildtools nur aus Zertifizierten Quellen, dies betrifft
- Den Compiler (Javac, gcc, etc.)
- Die Entwicklungsumgebung
- Die Buildtools (zB. Maven, Ant, Gradle)
- Die CI/CD Tools (z. B. Jenkins) - Die Analysetools
- Absicherung der Codeverwaltung
- Zum Beispiel Absicherung von Git durch geeignete Schlüssel statt Username/Passwort - Prüfung der eingesetzten Bibliotheken
- Download nur aus zertifizierten Quellen
- Einsatz von Bibliotheksverwaltungen mit White-/Blacklisting wie zum Beispiel Nexus IQ - Spezialisierte Tests von kritischen Codestellen
- Dies betrifft insbesondere Login und Rechteverwaltungen
- In besonders kritischen Fällen ist auch das Kompilat zu prüfen
Sie haben weitere Fragen zum Thema Secure Coding / Secure Development?
Wir freuen uns, von Ihnen zu hören!