Die geänderte Datei

In einem Projekt läuft ein Build auf einen Fehler. Die Details der Fehlermeldung zeigt auf eine bestimmte Javascript-Datei, eine Library. Nur, dass diese Datei den erwarteten Javascript-Library-Code gar nicht enthält, sondern statt dessen HTML, mit der Botschaft: „Diese Domain können Sie kaufen.“

Der Autor der Javascript-Library hatte augenscheinlich seine Domainregistrierung nicht verlängert, so dass die Domain an einen Grabber gefallen war. Dieser Domaingrabber lieferte nicht mehr das ursprüngliche Javascript, sondern statt dessen seine Werbung (als HTML) aus.

An dieser Anekdote ist leider nichts erfunden, sie ist dem Autor in einem konkreten Projekt genau so passiert. Es handelte sich um ein großes Enterprise-Projekt, in dem die Anwendungssicherheit kontinuierlich thematisiert wurde und durch verschiedene Reviews und Prozesse gestützt werden sollte. Aber die Buildumgebung hat man dabei weitgehend außen vor gelassen.

Zu Unrecht: Denn bei entsprechende krimineller Energie hätte auch Schlimmeres in der veränderten Datei stecken können als nur Werbung. So gesehen ist das Projekt haarscharf am eingangs erwähnten Alptraum vorbei geschrammt…

Die Spitze des Eisbergs

Nun hat sich dieses Projekt keineswegs außergewöhnlich leichtsinnig verhalten, sondern nur so gehandelt, wie es heute üblich ist: Sicherheitsanalysen konzentrieren sich auf den selbstentwickelten Anwendungscode und die Infrastruktur der Produktionsumgebung.

Mit dem eigenen Anwendungscode überprüft man aber nur einen kleinen Bruchteil dessen, was schlussendlich in der Produktionsumgebung läuft. Eine beispielhafte Lines of Code-Zählung einer gerade verfügbaren produktiven Ruby on Rails-Anwendung ergab die in der Abbildung dargestellten Zahlen.

Codeverteilung einer beispielhaften heutigen Anwendung
Codeverteilung einer beispielhaften heutigen Anwendung
Codeverteilung einer beispielhaften heutigen Anwendung
Zeilen Rubycode
eigene Software 6468
Gems (Libraries und Plugins) 558687
Application Server 22046

Das passt ins allgemeine Bild: Wir verdanken die Erfolge unserer Softwareentwicklung auch der immer leistungsfähigeren vorgefertigten Software, die wir benutzen.

Artefakte und Repositories

Wie findet solche vorgefertigte Software überhaupt den Weg auf unsere Produktionsumgebung?

Soweit es sich um OpenSource handelt, könnte man sich im Prinzip alles im Sourcecode besorgen und selbst bauen. Aber normalerweise greift man nur in besonderen Problemfällen auf diese Möglichkeit zurück, um z.B. einen störenden Bug zu fixen. In der Regel baut man Fremdsoftware nicht selbst, sondern besorgt sich entsprechende fertige Software-Artefakte aus dem Internet.

Dort und in der eigenen Umgebung nutzt man dabei „Repositories“. Ein Repository ist, kurz gesagt, ein Lager für Software, also ein Server oder ein Verzeichnis, in dem Softwareartefakte vorgehalten werden. Bei „Softwareartefakt“ denke man zunächst an Archive, also .jar, .gem, .war, .ear, .rpm, .deb oder ähnliche Dateien, aber auch an Imagedateien für virtuelle Maschinen.

Eine typische Buildumgebung

Diese Softwareartefakte werden zwischen den Repositories bewegt. Den Job des Spediteurs hat dabei in üblichen Projekten die eigene Buildumgebung, die ungefähr aufgebaut ist wie in Abbildung 1 gezeigt:

Abbildung 1: Eine typische Buildumgebung
Abbildung 1: Eine typische Buildumgebung

Am Anfang der Verarbeitungskette, die in so einer typischen Buildumgebung abläuft, steht ein neuer Stand des Sourcecodes im Versionsmanagement. Der Buildserver (oder Continuous Integration-Server) checkt diesen Stand aus. Er besorgt die passenden Libraries, die später in Produktion benutzt werden sollen, sowie die Plugins für das Buildsystem, die für Bauen und automatisiertes Testen benötigt werden.

Sind Bauen und Testen erfolgreich abgeschlossen, kopiert der Buildserver die in der Produktion benötigten Softwareartefakte in ein internes Repository, von wo aus sie (sofort oder später) in die Produktionsumgebung wandern.

Hier leistet also der Buildserver entscheidende Rangierdienste für Softwareartefakte. Er besorgt vorgefertigtes aus öffentlichen Repositories im Internet und stellt es, zusammen mit dem projektintern produzierten Anteil, im internen Repository für den weiteren Gebrauch zur Verfügung. Von Details einmal abgesehen (so kann ein Projekt durchaus mehrere interne Repositories nutzen) ist dieses Vorgehen heute üblich.

Nur in manchen Fällen setzt man auf eine schlankere Alternative, namentlich für interpretierte Sprachen, für die ein Compiliervorgang nicht nötig ist. Das Ergebnis eines Eierfolgreichen Builds ist dabei nur die Information, welcher Sourcecodestand erfolgreich gebaut wurde. Die Libraries werden nicht vom Buildserver aus über ein internes Repository auf die Produktionsumgebung geschoben, sondern zu einem späteren Zeitpunkt noch einmal neu aus dem Internet besorgt. Man kommt hierbei ohne dezidiertes internes Repository aus, bezahlt dafür aber einen Preis: Ein Teil der Buildserverfunktionalität, nämlich das Besorgen der Artefakte aus dem Internet, wird auf dem Produktionsrechner dupliziert.

Artefaktmanipulationen: Game over.

Libraries, oder allgemein Software-Artefakte, die später auf Produktion laufen werden, sind kritische Ressourcen. Wenn ein Angreifer hier manipulieren kann, hat er die Möglichkeit, eine Hintertür in die Produktionsumgebung einzubauen. Manipulierbarkeit von Libraries bedeutet also: In die Produktionsumgebung kann eingebrochen werden. „Game over.“

Ähnlich problematisch sind Manipulationsmöglichkeiten von Plugins. Mit „Plugin“ ist hier pauschal jede Software gemeint, die im Rahmen des Builds ausgeführt wird. (Bei dieser absichtlich weit gefassten Definition ist einerseits jeder benutzte Compiler ein Plugin. Andererseits sind auch viele Libraries gleichzeitig Plugins, sofern sie nämlich bei den automatisierten Tests im Rahmen des Builds aktiviert werden.) Fängt man sich ein Plugin mit Schadsoftware ein, so ergeben sich für den diese Schadsoftware kontrollierenden Angreifer vielfältige Möglichkeiten.

Wie beschrieben ist der Buildserver der zentrale Spediteur für Artefakte. Er hätte im Manipulationsfall technisch die Möglichkeit, nicht einfach nur zu transportieren, sondern auch zu manipulieren. Zum Beispiel könnte Buildserver-Funktionalität Hintertüren in soeben besorgte oder frisch erzeugte Libraries einschleusen, ehe sie ins interne Repository eingelagert werden. Das ist selbst von Softwaretests normalerweise möglich. Zwar erwartet man eigentlich, dass ein Softwaretest die Software nur testet und sie nicht ändert. Trotzdem ist in aller Regel auch vom Test aus Schreibzugriff auf Artefakte möglich und damit das Einschmuggeln von Schadcode. Wer als Angreifer ein beliebiges Plugin kontrolliert, kann also in der Regel auch Libraries manipulieren: „Game over.“

Und Manipulationen über Plugins sind leider verhältnismäßig unauffällig. Es ist ja eine genuine Aufgabe des Buildservers, Softwareartefakte im Internet zu besorgen. Man müsste schon recht genau hinschauen, um zu unterscheiden, ob Artefakte für legitime Buildaufgaben besorgt werden oder ob Schadsoftware, die sich in einem Plugin eingenistet hat, weitere Schadsoftware aus dem Internet nachlädt.

Die Produktionsumgebung ist diesbezüglich leichter abzusichern. Besorgt sich eine Produktionsumgebung neue Software aus dem Internet, sollten „Intrusion Detection“-Alarmglocken losgehen. Weiter lässt sich eine Produktionsumgebung mit etwas Zusatzaufwand so einrichten, dass benutzte Softwareartefakte nicht schreibbar, änderbar oder ergänzbar sind, jedenfalls nicht vom Standpunkt des Betriebssystem-Users, unter dem die Software läuft. Software mit einem User zu installieren und mit einem anderen laufen zu lassen sowie die entsprechende Einschränkung von Schreibrechten sind eigentlich schon seit Jahrzehnten Stand der Technik. Leider spart man hier in der Praxis immer noch häufig, namentlich im Zusammenhang mit der oben bereits erwähnten „schlanken Alternative“ für das Deployment bei interpretierten Sprachen.

Während es auf einer typischen Produktionsumgebung eine relativ überschaubare Fingerübung ist, die Rechtesituation entsprechend aufzuräumen, ist die Situation auf dem Buildserver komplexer. Es liegt in der Natur der Sache, dass der Buildvorgang als Ganzes Softwareartefakte erst schreibt und später ausführt. Installation und Laufzeit sind hier nicht so leicht sauber zu trennen wie in einer typischen Produktionsumgebung.

Die Situation wird noch verschärft, wenn viele verschiedene Buildjobs über einen gemeinsamen Buildserver abgewickelt werden. Zum Vergleich: Es gibt bei Applikation- und Webservern seit Jahren den Trend, in einer Serverinstanz nur noch eine Anwendung zu deployen. Man geht zunehmend dazu über, jeder Anwendung sogar eine komplette eigene (virtuelle) Maschine zu spendieren. Nur bei Buildservern ist der aktuelle Trend eher gegenläufig: Der einzelne Buildserver baut vielerorts eine stetig steigende Anzahl von Anwendungen.

Das vergrößert die Angriffsfläche: Ein Angreifer kann versuchen, zunächst ein obskures Plugin einer kleinen, eher unbedeutenden Anwendung zu infizieren. Ist das erreicht, sind auch die Anwendungs-„Flaggschiffe“ unmittelbar in Gefahr.

Und selbst die „Kronjuwelen“ sind gefährdet, der projekteigene Sourcecode. Für seine Aufgabe braucht ein Buildserver selbstverständlich lesenden Zugriff auf das Sourcecode-Versionsmanagement. Häufig gibt man ihm auch schreibenden Zugriff, zum Beispiel, damit er dort Tags erzeugen kann. Oft ist dieser Zugriff dann nicht administrativ auf das Neuerstellen von Tags beschränkt, sondern der Buildserver hat volles Schreibrecht. Das eröffnet einem Angreifer die technische Möglichkeit, vom Buildserver aus Hintertüren in den projekteigenen Sourcecode zu schmuggeln.

Das Internet – kein freundlicher Ort.

Ist das alles nur Theorie, nur, wie es so schön heißt, „Mathematik über der leeren Menge“? Oder besteht die reale Gefahr, dass ein Angreifer tatsächlich extern bezogene Softwareartefakte manipulieren kann?

Es ist uns heute längst selbstverständlich, nach Empfang einer unsignierten Email nicht ohne Weiteres auf ausführbare Attachments zu klicken: Auch, wenn die Email vorgibt, von einer vertrauenswürdigen Person zu kommen, kann sie gefälscht sein.

Aus dem selben Grund sollten wir uns scheuen, mit bloßem HTTP (ohne „S“) heruntergeladene Artefakte ohne Weiteres auszuführen. Es sind viele Angriffe gegen HTTP bekannt: DNS kann angegriffen werden, lokale eigene Router, das Routing im Buildrechner selbst oder (zum Beispiel mit BGP-Manipulationen) das Routing in den Tiefen des Internet. Hier Details auszuführen sprengt den Rahmen dieses Artikels. Aber zusammenfassend sei gesagt, dass der „Man in the Middle“ längst kein bloßes Hilfskonstrukt für die theoretische Analyse von Netzwerkprotokollen mehr ist, sondern tatsächliche praktische Realität.

Leider bedeutet selbst der konsequente Einsatz von HTTPS nicht, dass man sich entspannt zurücklehnen kann. Denn es gibt (zu) viele allgemeine anerkannte Zertifizierungsstellen. Vom Standpunkt eines Angreifers genügt es, nur einer von ihnen ein entsprechend mächtiges Zertifikat abzugewinnen, schon ist HTTPS kein Hindernis mehr für ihn. (Diese riesige Angriffsfläche versucht HPKP zu verkleinern.)

Und schon prinzipiell schützt HTTPS nur den Transportweg, gegen verfälschte Inhalte im fernen Repository ist es machtlos.

Übrigens helfen die vielfach anzutreffenden Prüfsummen weder bei der Enttarnung eines Man in the Middle-Angriffs noch bei einer Inhaltsverfälschung im Ursprungsrepository. Jedenfalls dann nicht, wenn die Prüfsummendatei aus derselben Quelle stammt wie der eigentliche Inhalt. Denn ein Angreifer kann und wird die zum verfälschten Inhalt passende Prüfsumme problemlos selbst erzeugen, um dem ahnungslosen Opfer beides konsistent zu präsentieren.

Interne Angreifer

Neben externen Angreifern, die von außen manipulieren, ist auch an interne Angreifer zu denken. Auch für sie kann es attraktiver sein, aus dem Internet besorgte Artefakte (nachträglich) zu verändern, um ihr Ziel auf diesem Weg zu erreichen.

Zugriff einschränken

Man kann die Angriffsflächen verkleinern, indem man den Buildserver unterteilt und jedem Teil nur die Zugriffsmöglichkeit einräumt, die er braucht (siehe Abbildung 2).

Abbildung 2: Abteilung
Abbildung 2: Abteilung

Die bereits weitgehend etablierten „Build Slaves“ bieten eine Möglichkeit, dies umzusetzen. Der hauptsächliche Buildserver (der „Master“) delegiert dabei alle eigentlichen Buildaufgaben an die Slaves. Für die wenigen verbleibenden Managementaufgaben benötigt er nur wenige (vielleicht sogar gar keine) Plugins aus dem Internet.

Die Build Slaves sind virtuelle Maschinen, die vor jedem Buildlauf auf einen definierten Ursprungszustand zurückgesetzt werden. Am Einfachsten werden sie komplett neu installiert, was durch die Möglichkeiten üblicher Virtualisierungslösungen erleichtert wird, Images zu verwalten.

In einem ersten Buildschritt besorgt so ein frisch aufgesetzter, sauberer Slave, der „Fetcher“, alle Artefakte aus dem Internet, die der anlaufende Build benötigt, die aber im eigenen internen Repository für externe Artefakte noch nicht vorhanden sind, und stellt sie dort zur Verfügung. Dieser erste Schritt nimmt alle Internetzugriffe vorweg, die vom gesamten Build benötigt werden. Da er nur diese einfache, eng umgrenzte Aufgabe hat, sollte dieser Schritt keine Plugins erfordert, oder nur wenige, die sich selten ändern.

Alle weiteren Schritte der Builds laufen ohne jede Internetanbindung ab, was durch entsprechende Firewallkonfiguration abgesichert ist. Ohne Internetanbindung wird das Leben für Angreifer schwerer.

Im folgenden Schritt wird der eigene Sourcecode gebaut. Dieser Schritt läuft ebenfalls auf einem frisch aufgesetzten, sauberen Slave ab, dem „Builder“. Hier wird noch nichts getestet, sondern es werden nur die eigenen Artefakte für die Produktionsumgebung gebaut. Der Builder hat dazu auf die Artifakte, die der Fetcher besorgt hat, Zugriff, aber nur lesenden. Der Builder stellt die Ergebnisse des Buildvorgangs, also die projektinternen Artefakte, in einem eigenen Repository zur Verfügung.

Ein dritter Schritt testet, was erster und zweiter Schritt zur Verfügung gestellt haben. Dieser Schritt läuft wiederum auf einem eigenen, sauber aufgesetzten Build Slave, dem „Tester“. Er entscheidet, ob das Buildergebnis im Test durchfällt oder in Produktion gebracht werden darf. Dazu können nun mehr und sich häufiger ändernde (Test-)Plugins genutzt werden. Wichtig ist dabei, dass der Tester auf alle internen Repositories nur noch lesenden Zugriff hat. Er kann keine Artefakte inhaltlich ändern.

Schreibrechte und Internetzugang so einzuschränken verringert Angriffsflächen. Es wird für einen Angreifer schwieriger, von einem Brückenkopf aus weiteres Terrain zu erobern. Hat ein (interner oder extern) Angreifer zum Beispiel ein Plugin unter Kontrolle, das bei der Testausführung benutzt wird, so muss er die Einschränkungen erst einmal überwinden, ehe er Produktionslibraries angreifen kann. Dazu ergibt sich für ihn die Schwierigkeit, dass sein Schadcode mangels Internetanbindung nicht bequem „nach Hause telefonieren“ kann.

So hilfreich sie sind, machen solche Einschränkungen allein Angriffe noch nicht unmöglich. So helfen sie nicht gegen Manipulationen an Libraries, die Teil der Produktionssoftware werden.

Unterschriften sinnvoll

Hilfreich gegen Manipulationen sind kryptographische Unterschriften (kryptographische Signaturen).

So eine Unterschrift kann der legitime Autor eines Artefakts mit Hilfe der Programme gpg oder gpg2 und seines privaten Schlüssels leicht erstellen.

Ein Angreifer benötigt den privaten Schlüssel des Autors, um einer manipulierten Version eines Artefakts den Anschein der Authentizität zu verleihen. Dieser private Schlüssel bleibt gut gehütetes Geheimnis des Autors und steht nicht öffentlich zur Verfügung.

Öffentlich zur Verfügung steht uns als Nutzern kryptographischer Signaturen der öffentliche Schlüssel des Autors (der zu seinem privaten Schlüssel passt). Ein Angreifer, der ein Artefakt manipuliert hat, steht ohne den privaten Schlüssel des Autors vor der für ihn unangenehmen Wahl: Er kann uns gar keine Signatur präsentieren, oder eine Signatur, die vom legitimen Autor erstellt wurde, die aber nicht zum manipulierten Artefakt passt, oder eine zum manipulierten Artefakt passende Signatur, die der Angreifer selbst erstellt hat, die aber zum öffentlichen Schlüssel des Autors nicht passt. Eine leicht automatisierbare Prüfung mit Hilfe des öffentlichen Schlüssels des Autors kann in jedem dieser Fälle die Alarmglocken auslösen und den Build abbrechen. Unterschriften, die auf dem veralteten MD5 basieren, sollten bei der Überprüfung grundsätzlich abgelehnt werden, die anderen verwendeten Algorithmen und Methoden gelten als sicher.

Glücklicherweise gibt es (schon seit Jahren) den Trend, dass immer mehr Repositories kryptographische Unterschriften ohnehin anbieten. Die Unterschriften sind also häufig bereits verfügbar – man muss sie nur prüfen.

Leider werden genau diese Prüfungen häufig unterlassen. Das liegt hauptsächlich an zwei Problemen: Zum einen ist die Softwareunterstützung für automatisiertes Überprüfen von kryptographischen Unterschriften noch recht durchwachsen. Es kommt auf das konkrete Buildtool an, ob man vorhandene Funktionalität einfach nutzen kann. Häufig muss man entsprechende Prüfautomatismen noch mehr oder weniger selbst implementieren. Glücklicherweise gibt es einen Trend hin zu flächendeckender Signaturüberprüfung, so dass wir für die Zukunft mehr und ausgereifte Lösungen erwarten können.

Das zweite Problem ist, dass eine Tabelle der öffentlichen Schlüssel erstellt und gepflegt werden muss. Diese Tabelle sollte außer den öffentlichen Schlüsseln auch eine Zuordnung hergeben, welche Artefakte von welchem Schlüssel beglaubigt werden dürfen.

Dabei hilft zunächst, dass es Repositories für Schlüssel im Internet gibt, die sogenannten „Keyserver“. Dort kann man sich alle relevanten öffentlichen GPG-Schlüssel bequem besorgen. Die gpg und gpg2 - Programme haben entsprechende Download-Funktionalität bereits integriert.

Allerdings kann jeder ohne Weiteres seine Schlüssel auf den Keyservern zur Verfügung stellen, ob ehrlicher Mensch oder Krimineller. Insofern ist es sinnlos, für Artefaktprüfung einfach beliebige Schlüssel zu akzeptieren, man benötigt die gut gepflegte eben erwähnte Schlüsseltabelle.

Sie erzeugt Aufwand. Zum einen entsteht einmaliger Aufwand für die initiale Zuordnung von Artefakten zu den öffentlichen Schlüsseln ihrer legitimen Autoren. Darüber hinaus entsteht leider auch fortlaufenden Pflegeaufwand. Denn die eigene Entwicklung schreitet fort und immer mal wieder will man ein neues Artefakt nutzen, für das die bisherige Tabelle noch keinen legitimen Autorenschlüssel kennt. Fortlaufender Pflegeaufwand entsteht weiter dadurch, dass es auch den Autoren der von uns genutzten Software genauso geht. Von einer Version zur nächsten kann jedes Artefakt schon mal neue Abhängigkeiten einführen, auf deren Dienste es sich neuerdings stützt. Aus der Projektperspektive erfordert das, immer mal wieder neue weitere Artefakte mit an Bord zu nehmen, auch wenn man selbst sie nur indirekt nutzt.

Trotz des Aufwandes führt an kryptographischen Signaturen derzeit kein Weg vorbei. Sie sind das Mittel der Wahl, Manipulationsrisiken zu senken.

Reproduzierbarkeit…

Kann man einen konkreten Buildlauf später ergebnisidentisch wiederholen, ist dieser Buildlauf reproduzierbar.

Nun kommen reproduzierbare Builds zunehmend aus der Mode. Man meint, sie nicht mehr zu brauchen. Man brauchte Reproduzierbarkeit traditionell, wo neue Versionen der eigenen Software in festem, relativ langsamen Releasetakt, alle soundsoviel Wochen oder Monate, in die Produktion gegeben wurden. Bei diesem (zunehmend veralteten) Releasetakt-Entwicklungsmodell wurden Features in einen stetig wachsenden Vorrat hinein entwickelt und überrollten dann zum Releasezeitpunkt gleichzeitig die Produktionsumgebung wie eine Art Tsunami.

Bei diesem Entwicklungsmodell wurde Reproduzierbarkeit aus einem etwas überraschenden Grund gebraucht: Weil es nie funktioniert hat. In der Praxis sind eben doch häufigere Softwarelieferungen nötig, ad hoc und zwischen den vorgeplanten Releaseterminen; Stichwort „Hotfix“. Diese Möglichkeit brauchte man für Bugfixes oder Sicherheitsupdates, die nicht wochen- oder gar monatelang warten konnten. In einer Hotfix-Lieferung sollte aber nicht der ganze inzwischen angestaute Änderungsvorrat mit ausgeliefert werden, sondern nur eine einzelne, kleine, kontrollierte Änderung; alles andere blieb vorerst, wie es beim letzten Release war.

Die Forderung war also, ad hoc einen Hotfix-Build durchführen zu können, der nur bezüglich einer einzelnen, kontrollierten Änderung vom letzten Releasebuild abweicht. Führte man nun einfach denselben Hotfix-Buildprozess ohne Änderung durch, so hatte man den Releasebuild reproduziert. Hotfix-Builds beherrscht also nur, wer auch reproduzierbare Releasebuilds beherrscht. Wer Hotfix-Builds braucht, für den ist Reproduzierbarkeit wichtig.

Dieser ursprüngliche Grund für die Anforderung „Reproduzierbarkeit“ ist inzwischen in vielen Projekten weggefallen. Moderne Projekte liefern nicht mehr in starrem, langsamen Releasetakt aus, sondern jedes Feature sofort, wenn es fertig wird. Dringende Bugfixes oder Sicherheitsupdates werden hierbei wie Features behandelt und ausgerollt, jeweils sofort, wenn sie fertig entwickelt worden sind.

Da sie nicht mehr benötigt wird, ist Reproduzierbarkeit für heutige Buildumgebungen keine Selbstverständlichkeit mehr. Es fehlt oft an Kleinigkeiten: Man weiß nicht mehr, welche Stände aus verschiedenem Sourcecodebäumen eigentlich in einen Build eingegangen sind. Die Buildumgebung baut immer einen festen verdrahteten Branch (master oder trunk), statt konfigurierbar zu sein. Der Deploymentprozess spült grundsätzlich die jüngsten Artefakte in die Produktionsumgebung. Diese Dinge sind behebbar, indem man entsprechende Konfigurationsmöglichkeiten einbaut.

Kritischer wird es, wenn die Steuerung des Builds nicht versioniert wird. Relativ häufig bietet der eigentliche Buildserver eine Web-UI an, über die im Laufe der Zeit mal dieses, mal jenes konfiguriert wird. Meist kann man nach einer Weile nicht mehr nachvollziehen, mit welcher Buildserverkonfiguration ein älterer Build damals gelaufen ist. Und übrigens auch nicht, wer eine konkrete Konfigurationsänderung eingepflegt hat. (Obendrein gibt es vom Buildserver selten ein Rezept zum automatischen Aufsetzen und gelegentlich noch nicht einmal Backups.)

Man könnte versuchen, das Problem mangelnder Nachvollziehbarkeit der Buildserver-Konfiguration dadurch zu lindern, dass man die Konfigurationsmöglichkeit auf wenige auserwählte „Buildadmins“ beschränkt. Dadurch entstehen leider schmerzhafte Reibungsverluste (mit entsprechenden versteckten Kosten), weil die einzelnen Mitglieder des Entwicklungsteams die Buildserverkonfiguration nicht mehr so anpassen können, wie es der Projektfortgang gerade erfordert. Andererseits lassen sich über die Buildserverkonfiguration normalerweise globale Plugins aktivieren. Böswilligen internen Angreifern bietet so eine Konfigurations-Web-UI Gelegenheit, üble Ziele wirkungsvoll zu verfolgen und dabei gleichzeitig nur wenig Spuren zu hinterlassen.

Als ein gangbarer Ausweg aus diesem Dilemma bietet sich an, zwar weiterhin allen Mitgliedern des Entwicklungsteams die Möglichkeit zu bieten, den Buildserver zu konfigurieren, aber sie zu zwingen, dabei klare und deutliche Spuren zu hinterlassen: Jede Konfiguration sollte in ein Versionsmanagementsystem aufgenommen werden und dort unmanipulierbar mit dem Namen des konfigurierenden Teammitglieds verbunden sein. Im Idealfall steuert die Web-UI den Buildserver nicht unmittelbar, sondern erzeugt lediglich die Änderungen für das Versionsmanagementsystem, aus dem heraus sich der Buildserver bedient und selbst konfiguriert.

Überhaupt ist Versionierung eine Maßnahme, die an vielen Stellen helfen kann, Reproduzierbarkeit zu erreichen. Vergleichsweise einfach ist es, alle Artefakte zu versionieren. Man kann hier ein Versionsmanagementsystem vorsehen, wie es auch für Sourcecode benutzt wird. Da „Branching and Merging“ hier aber nicht benötigt werden, genügt alternativ auch ein internes Repository, in das man neue Artefakte nur hinzufügen kann, alte aber nicht mehr löschen. Hat man so eine Lösung für Artefaktversionierung etabliert, so ist man nicht mehr auf die Mithilfe des Internets angewiesen, um Builds zu reproduzieren. Denn leider lehrt die praktische Erfahrung, dass in öffentlichen Repositories nicht jede kleine (Zwischen-)Version jedes Artefakts langfristig verfügbar bleibt.

… ist nützlich

Aber muss man überhaupt reproduzieren können?

Nicht unbedingt. Immer mehr Projekte kommen ohne Reproduzierbarkeit alter Builds aus. Aber es ist vom Standpunkt der Anwendungssicherheit extrem hilfreich, danach zu streben. Denn konzeptionell betrachtet ist „Reproduzierbarkeit“ eine Chiffre dafür, dass man detailliert weiß, wie der eigene Build funktioniert. Das Streben nach Reproduzierbarkeit führt automatisch dazu, dass man nach den „dunklen Ecken“ der eigenen Buildlandschaft sucht und sie ausleuchtet. Der eben ausgeführte Gedankengang ist dafür typisch: Der Build hängt davon ab, wie der Buildserver konfiguriert wird; für reproduzierbare Builds muss diese Konfiguration also ausgeleuchtet werden, zum Beispiel durch Versionierung. Lässt man dagegen solch eine dunkle Ecke unbeleuchtet, so besteht die Gefahr, dass ein Angreifer es sich dort häuslich einrichtet und sie zum potenten Brückenkopf ausbaut. Das Streben nach Reproduzierbarkeit übt also einen heilsamen Einfluss auf die Sicherheit der Buildumgebung aus.

Als wichtige Verteidigungsstrategie gerade gegenüber inneren Angreifern empfiehlt sich, zunächst jede Versionierung einzuführen, die für Reproduzierbarkeit nötig ist; darüber hinaus erfasse man zusätzlich für jede buildergebnisrelevante Änderung, welche Person dafür verantwortlich zeichnet. Hierfür bieten sich kryptographische Unterschriften an. Je flächendeckender man kryptographische Unterschriften und ihre automatisierte Kontrolle einführt, um so schwieriger wird es für einen internen Angreifer, keine Spuren zu hinterlassen. Darüber hinaus bietet die dafür nötige Infrastruktur die technische Grundlage, um Reviews und Vier-Augen-Prinzip fälschungssicher zu dokumentieren und automatisiert zu überprüfen.

Und schließlich hilft Reproduzierbarkeit der Forensik: Wacht man eines Morgens auf und findet sich im Alptraum eines gelungenen Angriffs auf die eigene Anwendung wieder, wird man froh sein über die Recherchemöglichkeiten, die eine auf Reproduzierbarkeit angelegte Buildumgebung bietet.

Fazit

Buildumgebungen, wie sie heute üblich sind, sind gegenüber bestimmten Angriffen anfällig und schlechter geschützt als die Anwendungen, die von ihnen gebaut werden.

Glücklicherweise stehen etliche Maßnahmen zur Verfügung, mit denen die Angriffsflächen verkleinert werden können. Der Kasten „Guter Rat“ fasst einige zusammen. Neben der bekannten DevOp-Empfehlung „Automate it!“ empfehlen sich „Version it!“ und „Sign it!“ als wichtige Bestandteile einer Sicherheitsarchitektur für Buildumgebungen. Was im konkreten Zusammenhang eines eigenen Projektes zweckmäßig und sinnvoll ist, gegen was man sich wie absichern und mit welchen Risiken man leben will, bleibt natürlich von Fall zu Fall zu entscheiden.

Guter Rat

Guter Rat

Jedes Projekt muss einen eigenen Maßnahmencocktail finden, um seine Angriffsfläche klein zu halten. Als potentielle Bestandteile für so einen Cocktail empfiehlt der Autor:

  • Verschlüsseltes Herunterladen Kein Herunterladen unsignierter Artefakte mit unverschlüsselten Protokollen (wie HTTP).

  • HPKP Um auch einen Angreifer scheitern zu lassen, der über den Master-Key einer Zertifizierungsstelle verfügt, benutzt man für das Herunterladen von Artefakten aus dem Internet bevorzugt Server, die HTTP Public Key Pinning nach RFC7469 implementieren, sowie innerhalb der eigenen Build-Infrastruktur entsprechende Clients.

  • Verschlüsselte interne Kommunikation Die interne Kommunikation zwischen dem Buildserver, dem Versionsmanagement, den eigenen Repositories, Testumgebungen und der Produktionsumgebung wird durch geeignete verschlüsselte Protokolle abgesichert (SSH, HTTPS, DAVS, …).

  • Schlüsselverwaltung Für diese interne verschlüsselte Kommunikation werden server- und clientseitig Schlüssel (und, im HTTPS-Fall, Zertifikate) benutzt. Für jeden Server und jeden Clienttyp oder sogar für jeden individuellen Client wird ein eigener Schlüssel generiert. Server und Client kontrollieren bei jeder Kommunikation gegenseitig die Gültigkeit der Schlüssel.

  • Keine Artefakte aus dem Internet direkt nach Produktion Aus der Produktionsumgebung heraus wird nicht auf das Internet zugegriffen, um Artefakte zu besorgen. Artefakte werden nur über interne Repositories in die Produktion gespielt.

  • Keine wiederholten Zugriffe Wenn sich die benötigte Version eines Artefakts seit dem letzten Build nicht geändert hat, wird für dieses Artefakt nicht mehr auf das Internet zugegriffen, sondern es wird einem internen Repository entnommen.

  • Artefakt-Besorgen ausgliedern Das Besorgen der Artifakte aus dem Internet wird aus dem restlichen Buildprozess herausgezogen und ihm in einem „Fetcher“ vorgelagert. Für den verbleibenden Buildprozess stehen die Artefakte in einem internen Repository zur Verfügung, auf das nur noch lesender Zugriff besteht; ein direkter Zugriff ins Internet wird unterbunden.

  • Schreibrechte einschränken Verschiedenen Phasen des Builds laufen getrennt ab, auf verschiedenen virtuellen Maschinen (oder wenigstens unter unterschiedlichen OS-Usern). Spätere Phasen haben nur lesenden Zugriff auf die Ergebnisse früherer Phasen.

  • Kryptographische Unterschriften nutzen Insofern die vom Build benutzten Repositories im Internet kryptographische Unterschriften bereithalten, werden sie zur Verifikation der heruntergeladenen Artefakte genutzt.

  • Kryptographische Unterschriften fordern. Bietet ein Repository im Internet noch keine kryptographischen Unterschriften für Artefakte an, so wird auf den Repositorybetreiber eingewirkt, sie einzuführen.

  • MD5-Unterschriften ablehnen Kryptographische Unterschriften auf Basis des veralteten MD5-Algorithmus gelten als unsicher und werden nicht akzeptiert. (Um zukunftsorientiert zu bleiben, wird der SHA1-Algorithmus möglichst auch vermieden.)

  • Legitime Unterschreiber unterscheiden Nicht jeder, der überhaupt etwas beglaubigen darf, darf schon alles beglaubigen. Unterschriften für ein Artefakt werden nur von Schlüsseln akzeptiert, die als zuständig für dieses betreffende Artefakt bekannt sind.

  • GPG-Keys durch Fingerprint identifiziert Jeder GPG-Key wird anhand seines kompletten „Fingerprints“ identifiziert, nicht nur anhand des kürzeren „Hexcodes“.

  • Buildserverkonfiguration versionieren Die Konfiguration des Buildservers wird vollständig versioniert.

  • Deploymentsysteme versionieren Die Konfiguration aller Deploymentsysteme, die benutzt werden, um Buildergebnisse in Produktion zu bringen, werden vollständig versioniert.

  • Automatisierung oder Protokollierung Deploymentschritte erfolgen automatisiert, um nachvollziehbar zu sein. Wo das nicht komplett möglich ist, sollten verbleibende manuelle Schritte möglichst simpel sein („Knopf drücken“ ist simpler als „Konfiguration editieren“) und protokolliert werden.

  • Definierter Startzustand Deployment Jedes Deployment fängt mit einem definierten, reproduzierbaren Startzustand aller Zielmaschinen an (VM oder reale Zielmaschine). Dies kann am einfachsten geschehen, indem alle Zielmaschinen für jeden Deploymentvorgang komplett neu aufgesetzt werden, einschließlich Betriebssystem („clean slate“).

  • Schreibrechte auf der Zielumgebung einschränken Die Software läuft auf der Produktionsumgebung so, dass sie sich selbst nicht ändern kann (keine Schreibmöglichkeit).

  • Definierter Startzustand Buildumgebung Jeder Build fängt mit einem definierten, reproduzierbaren Startzustand aller am Build beteiligten Maschinen an. Dies kann am einfachsten geschehen, indem alle Buildmaschinen vor jedem Buildvorgang komplett neu aufgesetzt werden, einschließlich Betriebssystem („clean slate“).

  • Artefakte versionieren Alle in einem Buildlauf benutzten und erzeugten Artefakte werden versioniert und archiviert.

  • Fälschungen der Artefakte verhindern Bei den Versionierungssystemen für Artefakte werden nachträgliche Änderungen durch digitale Signaturen unmöglich (oder zumindest entdeckbar) gemacht.

  • Fälschung Sourcecode verhindern Nachträgliche Änderungen (Geschichtsfälschung, „rewriting of history“) am Sourcecode oder der Buildserverkonfiguration werden verhindert oder zumindest entdeckbar gemacht, genauso wie das Segeln unter falscher Flagge (tatsächlicher und augenscheinlicher Autor einer Änderungen im Sourcecode sind nicht identisch). Das ist sowohl mit Subversion wie auch mit Git möglich, aber auf unterschiedlichem Weg: Subversion-Server setzen auf HTTPS auf und werden gut abgesichert; Subversion-Funktionsusern dürfen im Sourcecode überhaupt nicht ändern. In Projekten, die Git nutzen, versehen alle legitimen Git-Nutzer alle ihre Commits mit kryptographischen Unterschriften. Das ist in Git einfach zu automatisieren (und nach den Erfahrungen des Autors im praktischen Betrieb völlig schmerzfrei). Der zentrale Git-Server prüft die Signaturen aller eingehenden Änderungen.

  • Reviews Jede Änderung am Sourcecode, an der Buildkonfiguration, an einem Plugin oder einer Library wird in einem Review unterworfen. Der Reviewer dokumentiert das erfolgreiche Review mit digitaler Signatur.

  • Reproduzierbarkeit Jeder individuelle Buildlauf kann mit Hilfe der Versionierungen wiederholt werden.