In Zeiten von zunehmenden Cyberattacken wird es für Software-Produzenten immer wichtiger, ihre eigenen Produkte sicher zu machen. Dabei spielt es auch keine große Rolle, ob die Software im Intra- oder Internet läuft. Die Denkweise der Perimeter-Verteidigung („Drinnen“ und „Draußen“, vor und hinter der Firewall) ist nicht mehr zeitgemäß, da Angreifer immer wieder Wege finden, diese zu überwinden und Angriffe aus dem Inneren des Systems zu starten. Daher hat sich mein Kollege Björn Cordes Gedanken gemacht, was wir konkret tun können und diesen Blog-Artikel geschrieben.
Anwendungen sind heutzutage so komplex, dass kaum jemand die gesamte Software von Grund auf selbst schreibt. Um das Rad nicht selbst zu erfinden, wird oft auf Open-Source-Fremdbibliotheken zurückgegriffen. Die Sicherheit des eigenen Codes liegt natürlich in der eigenen Verantwortung, aber was ist mit diesen Fremdbibliotheken? Aufgrund von transitiven Abhängigkeiten werden meist so viele Fremdbibliotheken verwendet, dass eine manuelle Verfolgung der Entdeckung und Behebung von Sicherheitslöchern keine Option ist. Ständiges unreflektiertes Updaten auf die neusten Versionen ist auch keine besonders gute Idee, da hierdurch neue Sicherheitslöcher entstehen können, die vorher nicht existierten.
Der OWASP Dedenpency Check
Bei der Automatisierung der Problemlösung kann der OWASP Dependency-Check (https://owasp.org/www-project-dependency-check/) helfen. Im Kern handelt es sich um ein Kommandozeilentool, das sich Informationen zu bekannten Sicherheitslöchern aus einer öffentliche Datenbank („National Vulnerability Database“, kurz NVD) herunterlädt und die Liste der Fremdbibliotheken der eigenen Software mit diesen abgleicht. Das funktioniert übrigens nicht nur für Java-Anwendungen, sondern beispielsweise auch für JavaScript oder andere Anwendungen.
Heute ist es eigentlich nicht mehr notwendig, das Kommandozeilentool direkt anzubinden. Für die bekannten Build-Tools wie Jenkins, Gradle, Maven, Ant, NPM, usw. gibt es entsprechende Plug-ins, die die Integration erleichtern. Sie kümmern sich neben der Durchführung der Prüfung auch um die Erstellung von Reports.
Das dynamische Duo im Einsatz: Implementierung mit Gradle und Jenkins
In der Praxis hat sich bei Anwendungsfällen im Team eine Kombination aus Gradle und Jenkins bewährt. Das klingt natürlich zunächst unnötig kompliziert, kombiniert aber die jeweiligen Stärken der Beiden. Die eigentliche Prüfung führen wir mit Gradle durch. Das entsprechende Plug-in (https://jeremylong.github.io/DependencyCheck/dependency-check-gradle/index.html) bekommt über die Gradle-Infrastruktur eine erheblich tiefere Einsicht in das Projekt und findet dadurch mehr Bibliotheken zum Prüfen als das Jenkins-Plug-in. Außerdem kommt es gut mit Multi-Modul-Projekten zurecht. Das Reporting und das Steuern des Build-Status überlassen wir dann dem Jenkins-Plug-in.
Prüfen mit dem Gradle-Plug-in
Um das Gradle-Plug-in nutzen zu können, wird es über den bekannten Weg der Plug-In-DSL von Gradle eingebunden (s.a. https://plugins.gradle.org/plugin/org.owasp.dependencycheck). Die Konfiguration des Plug-ins findet dann ausschließlich über das Element dependencyCheck
statt. Der nachfolgende Code-Schnipsel zeigt dies beispielhaft:
format
– Als Ausgabeformat ist ‚XML‘ die richtige Wahl, da es das Eingabeformat für das Reporting im Jenkins ist.suppressionFile
– Wenn es bekannte, aber akzeptierte Sicherheitslöcher gibt, die nicht weiter berücksichtigt werden sollen, können diese in einer Konfigurationsdatei hinterlegt werden. Das kann Sinn machen, wenn ein Sicherheitsloch nur unter spezifischen Bedingungen zum tragen kommt, die im eigenen Kontext nicht gegeben sind und außerdem ein Fix aus Kompatibilitätsgründen nicht möglich ist. Diese Option ist mit Vorsicht anzuwenden!cve
– Hier können die URLs der NVD konfiguriert werden. Wenn man den OWASP-Dependency-Check in mehreren Projekten durchführt, lohnt sich die Einrichtung eines lokalen Mirrors (https://github.com/stevespringett/nist-data-mirror), der sich nächtlich mit der öffentlichen Datenbank synchronisiert. Die einzelnen Projekte können sich dann ihre Daten von diesem lokalen Mirror holen. Wenn die öffentliche Datenbank anstelle eines Mirrors genutzt werden soll, so kann der Eintragcve
vollständig weglassen werden.
Mit dem Plug-in stehen nun folgende Tasks in Gradle zur Verfügung:
Reporting mit dem Jenkins-Plug-in
Aktuell (Veröffentlichung des Artikels im Dezember 2021) gibt es ein Security Issue im Jenkins-Plug-in, für dass es noch keinen Fix oder Work-Around gibt.
Zunächst muss das Plug-in https://plugins.jenkins.io/dependency-check-jenkins-plugin/ im Jenkins installiert werden.
Im weiteren Schritt wird im Code der Jenkins-Pipeline der Gradle-Task dependencyCheckAggregate
aufgerufen und anschließend der Publisher des Jenkins-Plugins. Dem Publisher können noch verschiedene Grenzwerte (https://www.jenkins.io/doc/pipeline/steps/dependency-check-jenkins-plugin/) mitgeben werden. Im Beispiel werden keine Security-Issues der Kategorie Critical (höchste von vier Kategorien) akzeptiert. Last but not least wird im nachfolgenden Skript-Blog dann noch dafür gesorgt, dass der Buildstatus korrekt ausgesteuert wird.
Auf der Übersichtseite der Jenkins-Pipeline wird nach dem ersten Build eine Trendgrafik sichtbar. Auf der Detailseite der einzelnen Builds ist über den Menüeintrag auf der linken Seite eine Navigation zu den Details des Dependency-Checks möglich.
Jenkins-Pipelines als Teamplayer
In der Teamarbeit wurden gute Erfahrungen mit der Trennung von Jenkins-Pipelines nach Aufgabenstellung gemacht. Dabei haben die wichtigsten Projekte eine primäre Pipeline und eine sekundäre Pipeline
Die primäre Pipeline …
- ist zuständig für Compiling, Testing und Deployment.
- ist eine Multibranch-Pipeline, die für jeden Branch im Versionskontrollsystem einen Jenkins-Job erzeugt.
- wird durch Aktionen im Versionskontrollsystem (bspw. git push) getriggert.
- hat als wichtigste Merkmale Stabilität und Geschwindigkeit.
Die sekundäre Pipeline …
- ist zuständig für Tätigkeiten, die regelmäßig aber nicht bei jeder Änderung im Versionskontrollsystem durchgeführt werden müssen, und/oder zu lange dauern, um das Geschwindigkeitsziel der primären Pipeline zu erreichen (z. B. Lizenz-Check, OWASP-Dependency-Check).
- ist eine „normale“ Pipeline, die nur an den Hauptentwicklungszweig im Versionskontrollsystem angebunden ist.
- wird zeitgesteuert (einmal täglich) getriggert.
- ist unabhängig von der primären Pipeline und hat keine Seiteneffekte auf diese.
Ist dieser OWASP-Dependency-Check eigentlich alternativlos?
Neben dem OWASP-Dependency-Check gibt es noch andere – meist sprach- oder plattformspezifische – Alternativen. Bei einer NPM-basierten Javascript-Anwendung (oder einem Submodul) können die npm audit
-Funktion bzw. der npm-audit-ci-wrapper
in der Jenkins-Pipeline genutzt werden. Trotz der begrenzten Einsatzmöglichkeit (eben NPM-managed Javascript) gibt es einen Vorteil: Die Funktion npm audit
kommt mit einer kongenialen Partnerin npm audit fix
daher, die den Nutzer recht gut durch den Aktualisierungsprozess zur Behebung der Sicherheitslücken führt. Das Fixing ist im Gegensatz zum Audit allerdings nicht automatisierbar.
TL;DR – kurz zusammengefasst
Das Analysieren der eigenen Softwareprojekte in Hinblick auf Sicherheitslöcher ist eine notwendige und wichtige Aufgabe, die aber manuell nicht leistbar ist. Mit dem OWASP-Dependency-Check steht aber das Tooling zur Verfügung, um diese Aufgabe zu automatisieren. Wie im Artikel gezeigt, ist die Integration ins eigene Projekt mit wenigen Handgriffen möglich. Zuletzt bleibt nun noch die Angewöhnung daran, die Fremdbibliotheken stetig zu aktualisieren, um die Sicherheitslöcher zu stopfen.