Agile Softwareentwicklung hat sich längst zum Standard für viele Firmen etabliert. Softwareentwicklung und der IT-Betrieb arbeiten enger zusammen, Stichwort DevOps!
Was aber in der «DevOps-Bewegung» meist nicht oder nur sporadisch berücksichtigt wurde, ist das Thema Security.
Mit DevSecOps will man das Thema Security näher an den Entwicklungsprozess bringen, angefangen beim Ermitteln von Sicherheitsanforderungen in der Anforderungsanalyse, über automatische Sicherheitsüberprüfungen im CI/CD Prozess, bis hin zur Überwachung der laufenden Anwendung. In all diesen Bereiche, in denen die Sicherheit eine Rolle spielt, kommt DevSecOps zum Zug.
Dieser Blog beschreibt, wie wir das Thema DevSecOps bei uns umgesetzt haben, welche Themen wir bei der Umsetzung angeschaut haben, und wie wir dabei ein halbes Dutzend kritische Sicherheitslücken in einer internen Anwendung gefunden und beseitigt haben.
Aller Anfang ist schwierig, aber nicht unmöglich!
Als wir uns mit dem Thema DevSecOps auseinanderzusetzen begannen, haben wir uns zuerst über das Thema informiert und Fachbücher gelesen. Danach mussten wir entscheiden, welche Themen wir tatsächlich umsetzen wollten, und vor allem, wie?
Hier einige Fachbücher und nützliche Links zum Thema DevSecOps:
- Hands-On Security in DevOps
- Agile Application Security
- Securing DevOps-Safe services in the Cloud
- Epic Failures in DevSecOps: Volume 1
- OWASP DevSlop
Nachdem wir die Grundlagen über das Thema DevSecOps verstanden hatten, haben wir uns entschieden, dass wir das Thema «automatische Sicherheitsprüfungen im CI/CD Prozess» umsetzen wollen.
Dort geht es darum, dass während dem Build- und Deployment-Prozess automatische Sicherheitsprüfungen bei einer Anwendung durchgeführt werden.
Für die praktische Umsetzung konnten wir das interne Datenerfassungstool, welches von unseren Auszubildenden entwickelt und gepflegt wird, verwenden.
Es handelt sich um eine Webapplikation, welche in ASP.NET MVC und Azure DevOps umgesetzt wurde. Sie wird bei uns im internen Netzwerk von allen Mitarbeitern verwendet.
Welches ist das richtige Tool?
Es gibt verschiedene Möglichkeiten zur automatischen Sicherheitsprüfung von Anwendungen. Wir haben die folgenden eingesetzt:
- Dependency- und Secrets-Management bezieht sich auf die Überprüfung von bekannten Sicherheitslücken in den von der Anwendung verwendeten Fremdkomponenten sowie das Überprüfen von heiklen Daten im Quellcode, wie zum Beispiel Passwörtern oder Security-Tokens.
- Statische Sicherheitsprüfung (SAST = Static Application Security Testing) ist für die Überprüfung von Schwachstellen im Programmcode zuständig. Dabei wird der Programmcode Zeile um Zeile auf Programmierfehler überprüft. Bei manchen Tools kann dies in der Entwicklungsumgebung so integriert werden, dass diese Überprüfung schon beim Schreiben des Programmcodes visuell angezeigt wird, vergleichbar mit der Rechtschreibprüfung von MS Word.
- Dynamische Sicherheitsprüfung (DAST = Dynamic Application Security Testing) überprüft Schwachstellen einer Anwendung zur Laufzeit und gehört zur Kategorie Blackbox-Testing. Dabei wird die zu testende Anwendung ausgeführt und zum Beispiel mit einem Vulnerability Scanner auf Sicherheitslücken überprüft.
Auf der Suche nach den passenden Tools trafen wir auf eine grosse Vielfalt an Optionen.
Für unsere interne Anwendung wollten wir möglichst viele Bereiche abdecken und diese in der Azure-Build-Pipeline integrieren. Dabei haben wir uns für folgende Tools entschieden:
- Bereich Dependency-Management: OWASP Dependency Check
- Bereich Statische Sicherheitsprüfung: Security Code Scan
- Bereich Dynamische Sicherheitsprüfung: OWASP ZAP
Mit diesen konnten wir alle oben erwähnten Bereiche abdecken. Das beste dabei: Alle Tools sind kostenlos verfügbar und lassen sich z.T. mit ein paar wenigen Mausklicks in Azure DevOps integrieren!
Ein etwas holpriger Start…
Für die Integration der Tools in die bestehende Azure Build Pipeline haben wir den Ansatz gewählt, dass wir die Tools nacheinander installieren und anwenden, was sich im Nachhinein als richtig erwiesen hat.
Wir begannen mit der Integration des OWASP Dependency Checks. Dazu gibt es im Visual Studio Marketplace einen Build Task, der installiert und zur bestehenden Build Pipeline hinzugefügt werden kann. Danach konnten wir schon den ersten Scan durchführen. Das Resultat war nach ein paar Sekunden in Form eines HTML-Reports verfügbar.
Leider war das Ergebnis ernüchternd. Vor allem diejenigen Software-Bibliotheken, welche mit Sicherheitsrisiko Hoch eingestuft waren, mussten auf eine neue Version aktualisiert werden.
In der Regel ist das Aktualisieren von Software-Bibliotheken mit dem Paketmanager Nuget von Microsoft kein Problem, nur hatte jemand welcher die Anwendung entwickelt und erweitert hatte, gewisse Anpassungen in den zu aktualisierenden Software-Bibliotheken vorgenommen. Das hatte zur Folge, dass nach dem Aktualisieren die gesamte grafische Oberfläche der Anwendung nicht mehr korrekt angezeigt wurde. Dies galt es zuerst zu bereinigen, indem gewisse Anpassungen in der Anwendung vorgenommen werden mussten, bevor wir zum nächsten Schritt übergehen konnten.
Das ging aber einfach
Als nächsten Schritt haben wir die statische Code Analyse zuerst bei uns in der Entwicklungsumgebung und dann in der Build-Pipeline aktiviert.
Dazu setzten wir das Tool Security Code Scan ein, welches sehr einfach integriert werden kann und das die Resultate in der Entwicklungsumgebung anzeigt. Bei unserer Applikation wurde lediglich eine fehlende Anti-CSRF-Überprüfung in einem Controller festgestellt.
Hier ein Beispiel, wie die Resultate das Scans aussehen könnten:
Wo Rauch ist, ist auch Feuer…
Nachdem wir nun auch die statische Codeanalyse soweit hatten, war jetzt die Integration der dynamischen Sicherheitsprüfung an der Reihe.
Bevor wir aber OWASP ZAP in der Build-Pipeline einsetzen konnten, wollten wir die Anwendung zuerst lokal mit ZAP testen, um zu sehen ob unsere Anwendung überhaupt auf diese Weise getestet werden kann.
Die ersten Versuche, unsere Anwendung zu testen, führten dazu, dass die Anwendung schon nach wenigen Sekunden abgestürzt ist, weil ZAP während dem Scan eine grosse Vielfalt an unterschiedlichen Daten schickt und unsere Anwendung diese Eingaben nicht erwartet hat!
Das heisst, bevor wir richtig starten konnten, mussten wir unsere Applikation stabilisieren und auch unerwartete Eingaben behandeln.
Das Resultat des ersten komplett durchgeführten Scans sah wie folgt aus:
Im Resultat war ersichtlich, dass wir vier Meldungen hatten, welche mit dem Risk Level «High» eingestuft wurden. Nach einer genaueren Analyse stellte sich heraus, dass zwei davon False Positives waren und die restlichen beiden dieselbe Ursache an zwei verschiedenen Orten in der Anwendung hatten.
Wir konnten den Fehler tatsächlich bei uns in der Anwendung reproduzieren und mussten diesen zuerst beseitigen.
Bei der Sicherheitslücke handelte es sich um eine Cross-Site-Scripting-Lücke, welche aber nur Auswirkungen für den eigenen User (Self-XSS) hatte. Nichtsdestotrotz musste die Lücke geschlossen werden, weil XSS-Lücken welche als harmlos eingestuft werden, in gewissen Situationen zu kritischen Lücken mutieren können.
Der Anwendungsfall war, dass HTML-Tags bei der Dateneingabe erlaubt werden mussten, um dem User die Möglichkeit zu bieten, das Look-and-Feel selbst zu gestalten.
Leider wurde aber vergessen, dass man so auch JavaScript ausführen kann und deshalb die HTML-Tags «gesäubert» werden müssen. Die Lücke konnte durch den Einsatz der Software-Bibliothek HtmlSanitizer behoben werden, was wir mit einem erneut durchgeführten Scan verifizieren konnten.
Eines musste aber noch geklärt werden: In der Regel werden diese Art von Sicherheitslücken in ASP.NET mittels Request-Validation verhindert – trotzdem wurde eine XSS-Lücke gefunden. Wie ist das möglich?
Ein kurzes Code-Review brachte Licht ins Dunkel: Der Vorgängerentwickler hatte die Request-Validation auf globaler Ebene für alle Controller komplett ausgeschaltet! Der Grund war wahrscheinlich, dass er es ermöglichen wollte, HTML-Tags einzugeben, welche durch die Request-Validation verhindert worden wären. Wir haben dann die Request-Validation auf globaler Ebene wieder eingeschaltet und nur an der Stelle ausgeschaltet, welche durch die Software-Bibliothek HtmlSanitizer «gesäubert» wird.
Nachdem schliesslich der Scan lokal funktioniert hatte und wir alle Meldungen des Reports behoben hatten, konnten wir den Scan auch bei uns in der Build-Pipeline integrieren.
Der Mehraufwand hat sich gelohnt!
Warum sollte man eine automatisierte Sicherheitsüberprüfung anstreben?
Softwareentwicklung ist schwierig. Sichere Softwareentwicklung noch schwieriger!
Mit einer Automatisierung gewisser Sicherheitstests lassen sich viele Sicherheitslücken beheben. Gleichzeitig stellt man sicher, dass die Anwendung auch bei künftigen Anpassungen kontinuierlich weiter auf Sicherheitslücken getestet wird. Dabei gilt es aber zu beachteten, dass automatisierte Tests alleine nicht reichen, weil gewisse Sicherheitslücken nicht mit automatisierten Tests entdeckt werden können. Eine manuelle Sicherheitsprüfung muss ein wichtiger Bestandteil bleiben.
Fazit
- Tools schrittweise in die bestehende Umgebung einbauen – kein Big Bang!
- Aktualisierung von Software-Bibliotheken muss ohne grössere Quellcodeänderungen möglich sein. Am besten noch mit automatisierten Testtools überprüfen, ob nach einem Update die Anwendung tatsächlich noch richtig funktioniert.
- Die Resultate von Tools müssen auf «False Positives» untersucht werden.
- Benutzereingaben müssen immer als «gefährlich» behandelt und «gesäubert» werden.
- Wenn Sicherheitslücken gefunden werden, lohnt es sich, die genaue Ursache herauszufinden, um allfällige weitere Sicherheitslücken zu aufzudecken.
- Eingebaute Sicherheits-Features von Frameworks (wie z.B. Request-Validation in ASP.NET) nicht ausschalten, und wenn das unumgänglich ist, muss dieser ungesicherte Teil so klein wie möglich sein, und mit einem Code-Review überprüft werden.
- Zukünftige Änderungen werden automatisch auf Sicherheitslücken überprüft, was zur Folge hat, dass die Qualität der Software verbessert wird.