Bei der Erstellung von Softwaresystemen können ungewollt Sicherheitsschwachstellen mit eingebaut werden. Über diese Schwachstellen können Angreifer Kontrolle über das System gewinnen oder Daten daraus abziehen. Es stehen jedoch zahlreiche Hilfsmittel zur Verfügung, um das zu vermeiden.
Eine gute Übersicht über die am häufigsten auftretenden Sicherheitsprobleme geben die OWASP Top 10 [1]. Diese Schwachstellen möglichst früh im Entwicklungsprozess zu erkennen, bringt einige Vorteile:
-
Wenn mögliche Schwachstellen direkt im Entwicklungszyklus angezeigt werden, lernen die Entwickler:innen, diese in Zukunft direkt zu vermeiden. Das macht das System sicherer und spart Geld, weil weniger Fehler beseitigt werden müssen.
-
Selten durchgeführte, aufwendige Penetrationstests passen nicht zu agilen Vorgehensmodellen mit kurzen Auslieferungszyklen. Wenn man sich allein auf Penetrationstests verlässt, können in dazwischenliegenden Releases Schwachstellen in Produktion gebracht werden, die Angriffspunkte für mögliche Attacken sein können.
Schwachstellen können mit jedem Commit ins System kommen. Auch kann sich die Gefahrenlage mit der Zeit ändern, z. B. wenn neue Schwachstellen in genutzten Bibliotheken öffentlich bekannt werden. Deshalb ist es wichtig, Schwachstellenmanagement als einen kontinuierlichen Prozess aufzusetzen. Viele Tests zur Identifikation von Schwachstellen können automatisiert und damit organisch in den Entwicklungsprozess integriert werden. Dieser Artikel zeigt, welche Arten von automatisierten Testverfahren es gibt und stellt eine Auswahl von Werkzeugen mit Fokus auf Open-Source-Tools vor. Wir schauen uns außerdem an, wie sich diese Werkzeuge mit wenig Aufwand in den CI/CD-Prozess integrieren lassen, und beleuchten, wo ihre Grenzen und Fallstricke liegen.
Die Testarten
Es steht eine große Zahl an Open-Source- und kommerziellen Werkzeugen zur Verfügung, die man manuell zur Prüfung von Schwachstellen aufrufen kann, die sich aber zum größten Teil auch sehr gut automatisieren lassen. Diese Werkzeuge können in sechs Gruppen eingeordnet werden:
-
Software Composition Analysis (SCA), Prüfung der Abhängigkeiten: Moderne Systeme werden nicht von Grund auf neu geschrieben, sondern viele Basisfunktionen werden als Bibliotheken genutzt. Das gilt nicht nur für den Anwendungscode, sondern im Fall von Docker auch für Betriebssystemfunktionen und Programme. Diese Bibliotheken können bekannte Schwachstellen haben, die sich für Angriffe nutzen lassen.
-
Static Application Security Testing (SAST), statische Codeanalysen: Im Code lassen sich durch regelbasierte Suchen viele Probleme erkennen, z. B. Injections oder schwache Verschlüsselung. Es existieren Werkzeuge für alle gängigen Programmiersprachen und auch Infrastructure as Code (Dockerfiles, Helm Charts, Terraform, …).
-
Secrets Detection (SD), Suche nach Geheimnissen: Geheimnisse wie Passwörter oder API-Keys dürfen nicht mit dem Code in Repositories eingecheckt werden, und es gibt Werkzeuge, die z. B. Git Repositories über die gesamte Versionshistorie nach solchen Geheimnissen durchsuchen.
-
Infrastrukturtests: Auch die laufende Infrastruktur wie z. B. die Konfiguration von Cloud-Infrastrukturen, Kubernetes-Cluster, aber auch klassische Rechenzentrumsinfrastrukturen lassen sich sowohl mit Innensichten (Prüfungen, die innerhalb der Infrastruktur laufen) als auch Außensichten (Prüfungen von außen über das Internet) auf Schwachstellen überprüfen.
-
Dynamic Application Security Testing (DAST), Dynamische Tests: Blackbox-Sicherheitstests, bei denen die Tests durch Angriffe auf eine Anwendung (typischerweise Webanwendungen oder APIs) von außen durchgeführt werden. Die Tests können passiv sein und nur nach Auffälligkeiten suchen oder aktive Angriffe auf das System durchführen.
-
Interactive Application Security Testing (IAST): IAST arbeitet innerhalb einer Anwendung durch Instrumentierung des Codes, um Probleme zu erkennen und zu melden, während die Anwendung läuft.
In diesem Artikel konzentrieren wir uns auf die ersten vier Gruppen von Prüfungen, die alle einen statischen Charakter haben. Sie lassen sich sehr gut in den Build-Prozess integrieren.
Reduce your cyber security risk
New format in munich for the first time: 5 tracks | More than 20 sessions & workshops | Current topics in IT security | December 2 – 5, 2024 | Munich or online
Integration in CI/CD Pipelines
Ideal ist eine Integration dieser Tests direkt in die CI/CD Pipeline, sodass sie mit jedem Build durchlaufen werden und das Team so immer den aktuellen Stand kennt und schnell reagieren kann. Glücklicherweise sind quasi alle Testwerkzeuge für den Einsatz in Pipelines vorbereitet, sodass bei der Integration wenig Aufwand entsteht. Mit einem inkrementellen Ansatz, wie in Abbildung 1 gezeigt, können schnell erste Resultate gezeigt werden, die die Sicherheit des Systems verbessern.
Zum Start werden erste Werkzeuge in die Pipeline integriert, z. B. zur Prüfung der Abhängigkeiten im JavaScript-Code des Frontends oder zur statischen Analyse der Dockerfiles. Die Ausgabe der Ergebnisse erfolgt in einem lesbaren Format in der Konsole oder als Dateiexport. Damit kann das Team anfangen, die Ergebnisse der Prüfungen zu sichten und Schwachstellen zu beseitigen.
Die Analyse der Ergebnisse auf Basis von Konsolenausgaben oder Dateien ist mühsam. Häufig sind auch Ergebnisse zu finden, die im aktuellen Projektkontext False Positives darstellen, aber bei jedem Lauf der Pipeline wieder gemeldet werden. DefectDojo [2] ist ein Open-Source-Programm, das die Ergebnisse der Schwachstellentests einlesen kann und in einer Benutzeroberfläche darstellt. Darin können dann die Verwaltung und das Reporting der Schwachstellen erfolgen.
In weiteren Schritten werden zusätzliche Werkzeuge in die Pipeline aufgenommen, die mehr Informationen zu Schwachstellen erzeugen, bei deren Beseitigung das System noch sicherer gemacht wird.
Software Composition Analysis (SCA)
Viele Codebestandteile für neue Anwendungen stammen heute aus Bibliotheken. Diese können bekannte Schwachstellen enthalten, die Angreifer ausnutzen können. Das prüft man mit SCA. Die dafür verwendeten Tools nutzen verschiedene Quellen, z. B. die CVE-Datenbank [3] oder GitHub Advisories [4]. Kommerzielle Toolhersteller pflegen zusätzlich ihre eigenen Datenbanken mit Schwachstellen. Es stehen Tools für Anwendungen (Tabelle 1) und Infrastructure as Code (Tabelle 2) zur Verfügung.
Programmiersprache | Werkzeug | Link |
---|---|---|
Java u. a. | OWASP Dependency Check | https://jeremylong.github.io/DependencyCheck |
JavaScript | npm audit | https://docs.npmjs.com/cli/v7/commands/npm-audit |
.NET | dotnet list package –vulnerable | https://devblogs.microsoft.com/nuget/how-to-scan-nuget-packages-for-security-vulnerabilities |
Tabelle 1: SCA-Tools für Anwendungen
Infrastruktur | Werkzeug | Link |
---|---|---|
Docker Images | Trivy | https://github.com/aquasecurity/trivy |
Docker Images | Grype | https://github.com/anchore/grype |
Tabelle 2: SCA-Tools für Infrastructure as Code
Wie man mit den Ergebnissen umgeht
Die Aktualisierung einer betroffenen Komponente auf eine neuere Version ist häufig der einfachste und beste Weg, eine gefundene Schwachstelle zu beseitigen. Manchmal ist das aber nicht möglich, weil zum Beispiel die Schwachstelle in der Komponente noch nicht gefixt ist oder weil die Komponente eine transitive Abhängigkeit ist. In diesen Fällen gibt es mehrere Möglichkeiten, mit der Schwachstelle umzugehen:
-
Sicherstellen, dass die anfällige Funktion der Komponente nicht im System verwendet wird.
-
Einkapseln der anfälligen Funktion der Komponente im eigenen Code, damit die Schwachstelle nicht mehr von Angreifern genutzt werden kann.
-
Explizite Akzeptanz des Risikos.
Static Application Security Testing (SAST)
Programmierer können unbewusst Schwachstellen in ihren Code einbauen. Durch ein manuell gebautes SQL für eine Performanceoptimierung kann schnell eine Angriffsfläche für SQL Injections entstehen. Werden Ein- und Ausgaben in Weboberflächen nicht sorgfältig geprüft, kann ein Einfallstor für Cross-Site Scripting entstehen. Oder sensitive Daten sind durch falsche Konfiguration der Kryptografie nur schwach verschlüsselt. Mit regelbasierten Suchen lassen sich viele dieser Probleme erkennen. Es existieren Werkzeuge für alle gängigen Programmiersprachen (Tabelle 3) und Infrastructure as Code (Tabelle 4).
Programmiersprache | Werkzeug | Link |
---|---|---|
Java | FindSecBugs | https://find-sec-bugs.github.io |
JavaScript | ESLint | https://eslint.org |
.NET | Security Code Scan | https://security-code-scan.github.io |
Verschiedene | Semgrep | https://semgrep.dev |
Verschiedene | GitLab SAST | https://docs.gitlab.com/ce/user/application_security/sast |
Tabelle 3: SAST-Tools für Programmiersprachen
Infrastruktur | Werkzeug | Link |
---|---|---|
Verschiedene | Checkov | https://www.checkov.io |
Verschiedene | KICS | https://kics.io |
Verschiedene | Terrascan | https://docs.accurics.com/projects/accurics-terrascan/en/latest |
Terraform | tfsec | https://tfsec.dev |
Kubernetes | Kubesec | https://kubesec.io |
Tabelle 4: SAST-Tools für Infrastructure as Code
Wie man mit den Ergebnissen umgeht
Wenn eine gefundene Schwachstelle nicht als False-Positive-Meldung eingestuft wird, ist es immer sinnvoll, den Source Code entsprechend zu ändern, um die Schwachstelle zu entfernen. Die Werkzeuge geben dabei häufig Hilfestellung, indem sie Beispiele für eine korrekte Implementierung enthalten.
Secrets Detection (SD)
Geheimnisse wie Passwörter oder API-Keys sind schnell einmal im Code-Repository eingecheckt. Beispiele dafür sind Informationen, die im Code hart verdrahtet wurden oder ein versehentlicher Commit einer Konfigurationsdatei. Wenn das Code-Repository nicht sehr strikt beschränkt, wer darin lesenden Zugriff hat, können dadurch möglicherweise weitreichende Angriffsstellen entstehen, mit denen z. B. Unbefugte direkt auf Datenbanken oder Schnittstellen zugreifen können. Solche Daten dürfen also nicht mit dem Code in Repositories eingecheckt werden, und es gibt Werkzeuge, die z. B. Git Repositories über die gesamte Versionshistorie nach solchen Geheimnissen durchsuchen (Tabelle 5).
Werkzeug | Link |
---|---|
detect-secrets | https://github.com/Yelp/detect-secrets |
truffleHog3 | https://github.com/feeltheajf/trufflehog3 |
Gitleaks | https://github.com/zricethezav/gitleaks |
Tabelle 5: Werkzeuge für Secrets Detection
Wie man mit den Ergebnissen umgeht
Es reicht nicht aus, das Geheimnis aus dem Code oder der Konfigurationsdatei zu entfernen und eine neue Version in das Repository einzuchecken. Da die Information weiterhin in den vorherigen Versionen sichtbar ist, müssen die Passwörter oder API-Keys selbst in den Konfigurationen geändert werden. Das kann viel Aufwand bedeuten, ist aber der einzige Weg, die Systeme in diesen Fällen sauber abzusichern.
Infrastrukturtests
Die vorher beschriebenen Tests werden in die CI/CD-Pipeline eingebunden und liefern Ergebnisse direkt im Entwicklungsprozess. Es kann aber auch die installierte Infrastruktur selbst geprüft werden. Die eingebauten Werkzeuge der großen Cloud-Provider sowie Open-Source-Tools erzeugen eine Innensicht auf mögliche Schwachstellen in der Konfiguration (Tabelle 6).
Infrastruktur | Werkzeug | Link |
---|---|---|
AWS | Prowler | https://github.com/toniblyx/prowler |
AWS | Security Hub | https://aws.amazon.com/security-hub/ |
Azure | Security Center | https://azure.microsoft.com/services/security-center/ |
Kubernetes | kube-bench | https://github.com/aquasecurity/kube-bench |
Kubernetes | kube-hunter | https://github.com/aquasecurity/kube-hunter |
Tabelle 6: Tools für Infrastrukturtests
Wie man mit den Ergebnissen umgeht
Wie auch bei den anderen Prüfungen muss zunächst eine Bewertung erfolgen, wie relevant die Meldung im aktuellen Kontext ist. Eventuell soll eine im Internet zu erreichende Ressource tatsächlich öffentlich zu erreichen sein, oder es ist eine Fehlkonfiguration, die behoben werden muss. Wenn die Einrichtung der Umgebung mit Infrastructure as Code geschieht, können bereits in einer Entwicklungs- oder Testumgebung viele Probleme gefunden werden, was dann zu einer sicheren Einrichtung der Produktionsumgebung führt. Dennoch sollten in allen Fällen die Schwachstellentests auch auf der produktiven Umgebung durchgeführt werden.
Grenzen und Fallstricke
Mit den hier vorgestellten Vorgehensweisen und Werkzeugen kann man die Sicherheit eines Softwaresystems stark verbessern. Man muss sich aber auch der Grenzen und Fallstricke bewusst sein.
Sicherheit im Design
Auch wenn die genannten Werkzeuge ein integraler Bestandteil der Entwicklung sein sollten, fängt Sicherheit dennoch früher im Entwicklungsprozess an. Die Anforderungen an Sicherheit müssen klar definiert sein. Um diese zu ermitteln, helfen Schutzbedarfs- und Bedrohungsanalysen und daraus abgeleitete Risiken. Die Sicherheitsanforderungen werden in den User Stories und der Definition of Done verankert und sind damit auch Bestandteil der Architektur des Systems.
Automatisierte Prüfungen vs. Sicherheitstests
Die automatisierten Prüfungen können nur einen Teil der Schwachstellen erkennen. Wenn die Sicherheitsanforderungen aber in den User Stories und der Definition of Done beschrieben sind, wird es ein natürlicher Prozess, diese Anforderungen auch mit den verschiedenen im Projekt etablierten Testmethoden zu überprüfen, von Unit-Tests über Integrationstests bis Ende-zu-Ende-Tests.
Auch Penetrationstests haben weiterhin ihre Berechtigung. Mit einer Mischung aus automatisierter Schwachstellenerkennung und expliziten Tests der Sicherheitsanforderungen verlieren Penetrationstests aber ihren Schrecken und werden mehr zu einer Bestätigung, dass die Maßnahmen für sicheres Design und Entwicklung funktionieren.
Auswahl der Tools
Die in den vorherigen Kapiteln aufgezählten Tools sind nur eine kleine Auswahl. Es gibt viele weitere Tools, sowohl als Open Source als auch kommerzielle Produkte. Open-Source-Tools können schon gute Ergebnisse liefern, kommerzielle Produkte haben jedoch häufig bessere Analysemethoden und Managementoberflächen, für die man allerdings auch bezahlen muss.
Wie viel Aufwand man in die Auswahl von Tools investiert und ob kommerzielle Produkte notwendig sind, hängt letztendlich von den Sicherheitsanforderungen ab. Für niedrige bis mittlere Anforderungen wird ein kurzer Auswahlprozess von Open-Source-Tools vollkommen ausreichen, damit kann man mit wenig Investition die Sicherheit deutlich verbessern. Bei höheren Sicherheitsanforderungen, oder wenn man nicht für ein einzelnes Projekt, sondern auf Unternehmensebene denkt, ist auch ein höherer Aufwand für die Werkzeugauswahl und die Investition in kommerzielle Produkte gerechtfertigt.
False Positives
False Positives sind gemeldete Schwachstellen, die im aktuellen Kontext keine Beeinträchtigung der Sicherheit darstellen. Insbesondere die statischen Codeanalysen neigen dazu, viele False-Positive-Ergebnisse zu produzieren, aber auch bei den anderen Klassen der Werkzeuge treten sie auf. Bei der Konfiguration der Tools gibt es zwei Möglichkeiten, ihre Anzahl zu reduzieren:
-
Ausschluss von Code, der zur Laufzeit nicht ausgeführt wird (z. B. Testcode oder Eingaben für die Codegenerierung) und eingebundenem Code wie JavaScript-Bibliotheken (z. B. jQuery oder Bootstrap)
-
Anpassung der Regelsätze der Scan-Engine; man kann zum einen Regeln für Bedingungen deaktivieren, die im aktuellen Kontext als sicher gelten können, oder bestehende Regeln ändern, damit sie besser zum Code passen
Wird DefectDojo zum Management der Schwachstellen eingesetzt, können die False Positives auch dort markiert werden. Damit entfällt ihre Behandlung für die Zukunft.
Bewertung und Priorisierung
Idealerweise wird man die Prüfungen zum Start des Projekts in die CI/CD-Pipeline einbauen. So kann man gefundene Probleme schnell beseitigen und es entstehen gar nicht erst lange Listen an abzuarbeitenden Problemen. Werden die Prüfungen erst aufgesetzt, wenn die Codebasis schon größer ist, erhält man typischerweise zunächst einmal eine längere Liste von gefundenen Schwachstellen. Diese müssen nach Relevanz bewertet und priorisiert werden. Dabei helfen die von den Werkzeugen vergebenen Schweregrade, sodass man sich von kritischen Meldungen zu den weniger schwerwiegenden durcharbeiten kann. Diese Schritte stellen einen Initialaufwand für das Projekt dar, der nicht unterschätzt werden darf und eingeplant werden muss.
Praktisches Beispiel
Wie sieht das Ganze in der Praxis aus? Eine kurze GitLab Pipeline zeigt, wie man eine Analyse der Abhängigkeiten und eine statische Codeanalyse mit wenig Aufwand einbinden kann.
Unser Beispiel-Service ist eine Spring-Boot-Anwendung, die mit Gradle gebaut wird. In der Datei build.gradle müssen wir nur einige zusätzliche Zeilen einfügen, um den OWASP Dependency Check für die Analyse der Abhängigkeiten und FindSecBugs für die statische Codeanalyse aufzurufen (Listing 1).
Listing 1
plugins {
...
id "org.owasp.dependencycheck" version "6.5.0.1"
id "com.github.spotbugs" version "4.7.9"
}
...
dependencies {
...
spotbugsPlugins 'com.h3xstream.findsecbugs:findsecbugs-plugin:1.11.0'
}
dependencyCheck {
format='ALL'
}
spotbugs {
ignoreFailures = true
}
In unserer GitLab CI/CD Pipeline führen wir drei zusätzliche Schritte ein, die in Abbildung 2 zu sehen sind:
-
checkVulnerabilities führt sowohl die Prüfung der Abhängigkeiten als auch die statische Codeanalyse durch. Diese Prüfungen könnte man natürlich auch direkt beim Bauen der Anwendung durch Gradle aufrufen lassen. Die Berichte werden im Ordner build\reports abgelegt, dessen Inhalte als Artefakte für die nachfolgenden Schritte gespeichert werden.
-
import_findsecbugs und import_dependency_check importieren die gefundenen Schwachstellen in DefectDojo. Dazu nutzen wir den dd-import-Wrapper [5], der die benötigten Konfigurationen in DefectDojo bei Bedarf selbst anlegt und die API-Parameter als Umgebungsvariablen abfragt (Listing 2), was sehr gut zu der Definition von CI/CD Pipelines passt.
Damit stehen alle Ergebnisse in DefectDojo zur Auswertung und zur weiteren Behandlung bereit (Abb. 3). Typische Maßnahmen sind:
Listing 2
variables:
DD_PRODUCT_TYPE_NAME: "Research and Development"
DD_PRODUCT_NAME: "Example Service"
DD_ENGAGEMENT_NAME: "GitLab"
stages:
- check
- import
checkVulnerabilities:
stage: check
image: openjdk:11-jdk
script:
- chmod u+x gradlew
- ./gradlew dependencyCheckAnalyze spotbugsMain
artifacts:
paths:
- build/reports/
when: always
expire_in: 1 day
import_findsecbugs:
stage: import
image: maibornwolff/dd-import:1.0.3
needs:
- job: checkVulnerabilities
artifacts: true
variables:
GIT_STRATEGY: none
DD_TEST_NAME: "FindSecBugs"
DD_TEST_TYPE_NAME: "SpotBugs Scan"
DD_FILE_NAME: "build/reports/spotbugs/main.xml"
script:
- /usr/local/dd-import/bin/dd-reimport-findings.sh
import_dependency_check:
stage: import
image: maibornwolff/dd-import:1.0.3
needs:
- job: checkVulnerabilities
artifacts: true
variables:
GIT_STRATEGY: none
DD_TEST_NAME: "Dependency Check"
DD_TEST_TYPE_NAME: "Dependency Check Scan"
DD_FILE_NAME: "build/reports/dependency-check-report.xml"
script:
- /usr/local/dd-import/bin/dd-reimport-findings.sh
-
Die Auffälligkeit bleibt offen und soll im Code oder durch Aktualisierung einer Bibliothek beseitigt werden.
-
Die Auffälligkeit ist ein False Positive und kann geschlossen werden.
-
Die Auffälligkeit wird durch andere Maßnahmen mitigiert und kann geschlossen werden.
-
Das durch die Auffälligkeit entstandene Risiko wird akzeptiert.
Diese Entscheidungen bleiben stabil, wenn bei weiteren Läufen der Pipeline die Ergebnisse neu importiert werden, d. h. geschlossene Auffälligkeiten bleiben mit ihrer Begründung geschlossen. Zusätzlich werden Auffälligkeiten automatisch geschlossen, wenn sie beispielsweise durch eine Änderung nicht mehr im Code enthalten sind und somit keine Meldung mehr entsteht. So reduziert sich die Menge der offenen Auffälligkeiten mit der Zeit und reflektiert, dass die Sicherheit des Systems wächst.
Fazit
Automatisierte Schwachstellentests können gut in bestehende CI/CD Pipelines integriert werden und helfen, frühzeitig mögliche Sicherheitsprobleme zu vermeiden. Initialaufwände entstehen bei Auswahl und Konfiguration der Tools und bei ersten Prüfungen. Diese Aufwände schwingen sich aber schnell ein und das Reagieren auf Schwachstellen wird Teil der laufenden Entwicklung.
Anwendungen, die geschäftskritische Vorgänge abwickeln oder sensible Daten verwalten, sollten Schwachstellentests der hier vorgestellten Kategorien einsetzen. Mit weiteren sicherheitsrelevanten Maßnahmen wie Architektur- und Codereviews, funktionalen Sicherheitstests und Penetrationstests ergibt sich eine optimale Absicherung der Anwendung gegenüber Angriffsversuchen.