DevSecOps – Security kriegen wir schon dran!

Anfang des Jahres haben wir in unserem Blog bereits “high-level” über Pentests im agilen Umfeld geschrieben. Dass Security irgendwie schon zum DevOps-Gedanken dazu gehört, da sind sich die meisten wohl schnell einig. Wie das genau aussehen soll – naja. Da wir Automatisierung und Websicherheit zu unseren Kernthemen zählen, möchten wir die Gelegenheit hier nutzen, um auf technischer Ebene am Beispiel von Jenkins mit SSLyze zu zeigen, wie einfach die ersten Schritte gemacht sind. In unserem git-Repo finden sich auch die Scripte für eine einfache erste Integration des freien Prüfwerkzeugs für Webanwendungen OWASP Zap.

Security in der Pipeline

Betrachten wir die gesamte CI/CD-Pipeline vom Coding bis zum Betrieb, dann stoßen wir auf unterschiedliche Ebenen und Tools, die uns im Bereich der IT-Sicherheit helfen können. In diesem Artikel möchten wir ein praktisches Beispiel liefern – daher hier nur ein kurzer Exkurs in die Werkzeuge und Ebenen ohne Anspruch auf Vollständigkeit.

Werkzeuge und Ebenen

Alles beginnt für uns mit Code. Der sollte funktionieren, grundlegend ordentlich aussehen und syntaktischen Vorgaben entsprechen. Darüber hinaus sollte der Code natürlich “sicher” sein:

  • Statische Code Analyse, Lint-Tests, Unit-Tests

Der eigene Code wird schnell über Bibliotheken und Frameworks erweitert. Diese liegen nicht immer im Quellcode vor, können aber Sicherheitslücken aufweisen:

  • Dependency-Checks, Versionsüberprüfungen auf Schwachstellen

Aus dem Code und den Bibliotheken entsteht eine Anwendung. Diese wird lauffähig auf einem System deployed und soll natürlich sicher sein:

  • Dynamische Anwendungstests / Pentests

Das “Ops” in DevOps verdeutlicht, dass wir als Entwickler mit dem Deployment nicht mehr am Ende sind. Die Software läuft auf einem Dienst, der wiederum auf einem Betriebssystem läuft. Jede dieser Ebenen kann Schwachstellen enthalten, die es – auch im laufenden Betrieb – zu vermeiden gilt:

  • Härtungsprüfungen / Konfigurationsprüfungen
  • Schwachstellenscans

Die finale Pipeline enthält zu jedem dieser Punkte mindestens ein Prüfwerkzeug. “Mindestens?” – ja. Insbesondere im Bereich der dynamischen Webanwendungstests werden Sie schnell feststellen, dass jedes Tools so seine Vor- und Nachteile hat und sich mehrere Tools in den Befunden in vielen Fällen ergänzen. Eine Orchestrierung von Konfigurationen und Scripten folgt in der Regel ebenfalls recht schnell auf die ersten Gehversuche.

80-20-Prinzip

Natürlich können wir uns auf dieser Ebene verkünsteln. Schnell finden sich bis zu 5 freie Werkzeuge für die Durchführung von sicherheitsrelevanten Web-Prüfungen. Bei Schwachstellen- und Quellcodescannern gibt es auch eine solide Auswahl. Hier ist unsere klare Empfehlung: Macht IT-Security agil. Keine langwierigen Konzepte und Evaluierungen bei der initialen Integration von “Sec” in den “DevOps”-Gedanken. Lieber ist etwas da, was nicht perfekt ist, als dass es noch Jahre diskutiert wird.

Für den Start gibt es kein falsches Tool, keinen falschen Weg und keine falsche Zuweisung von Verantwortlichkeiten. Jede Organisation tickt ein Stück weit anders. Jede Organisation steht im Reifegrad der Entwicklung an einer anderen Stelle. Jedes Teams weist unterschiedliche Charaktere und Profile auf.

Also fangen wir an. Mit einer Entscheidung für einen Weg und ein Werkzeug.

Die Komponenten

In der agilen Entwicklung benötigen wir eine zentrale Instanz, die sich um die CI/CD-Pipeline kümmert.

Jenkins

Jenkins ist frei verfügbar und erfreut sich großer Beliebtheit. Dabei ist Jenkins natürlich kein muss. Die folgenden Beispiele lassen sich im Grunde mit jedem System nachstellen, was Befehle in einer vorab festgelegten Abfolge ausführen und dynamisch auf die Ergebnisse reagieren kann. Um hier einen etwaigen Austausch zu erleichtern und auf etablierte Script-Sprachen zu setzen, versuchen wir möglichst viel Funktionalität nicht im Jenkins selber abzubilden – auch weil die Wiederverwendbarkeit der Scripte dadurch deutlich größer ist.

Security Tools

Was die Tool-Auswahl für die Überprüfung der Webanwendung angeht, bleiben wir mit SSLyze und OWASP Zap (s. git-Repo) für den Start bei zwei frei verfügbaren Werkzeugen. Die Auswahl ist dabei für die beispielhafte Aufbereitung des Themas und den Durchstich gedacht und stellt keine abschließende Liste von Empfehlungen für die Pipeline dar.

  • OWASP Zap prüft die Sicherheit der Anwendung und einige Parameter der Service- und Betriebssystemkonfiguration
  • SSLyze prüft ausschließlich die TLS-Konfiguration des Dienstes

Weitere Tools wie SonarQube als Quellcode-Scanner, der OWASP Depency-Check für Drittkomponenten oder OpenVAS als Schwachstellenscanner lassen sich analog dazu in diese oder eine andere Pipeline einbinden.

Scripting: Python

Security-as-Clicki-Bunti: ganz so leicht wird es nicht. Auch wenn wir freie Tools für die eigentlichen Sicherheitsüberprüfungen verwenden, so müssen wir die Aufrufe und die Auswertung der Ergebnisse an vielen Stellen selbst in die Hand nehmen. Jenkins bringt mit Groovy  eine mächtige Script-Sprache mit, die dazu grundsätzlich in der Lage ist. Allerdings ist Groovy nicht unbedingt in der Hot-List der Script-Sprachen. Wir bemühen Python – auch, um die Scriptlets an anderer Stelle einfach wiederverwenden zu können und weil viele Security-Tools ohnehin schon auf Python aufbauen. Etliche Tools sind auch über Python-pip paketiert und somit einfach zu installieren.

Der Aufbau

Wo steht welche Komponente? Wer ist verantwortlich für welchen Prozess? Schon die Diskussion über die Erweiterung der bestehenden Pipeline-Infrastruktur kann Workshop-Stunden füllen. Das folgende Beispiel versucht davon losgelöst die ersten Schritte zu gehen.

Jenkins + Kali-Linux

Wir möchten die Security-Tools nach Möglichkeit auf einem separaten System haben. Für Quellcode-Scanner ist das nicht immer sinnvoll, für die hier vorgestellten Werkzeuge bietet es aber einige Vorteile:

  • Wir installieren nicht unzählige Werkzeuge und Abhängigkeiten auf den Build-Servern
  • Die Werkzeuge belasten nicht die Build-Server (Arbeitsspeicher, Netzwerk)
  • Die Werkzeuge können auch außerhalb der Pipeline verwendet werden
  • Wir können eine beliebige vorgefertigte Distribution verwenden

Wir betreiben also einen eigenen Security-Tool-Server. Der Einfachheit halber nehmen wir den Pentest-Klassiker KALI-Linux. Für die ersten Gehversuche sind alle Tools vorhanden oder über die eingebundenen Sourcen per Paketmanager einfach zu installieren. Abhängigkeiten und Libraries sind in den meisten Fällen in den passenden Versionen vorhanden: Es funktioniert halt einfach.

Das Ergebnis wird etwa so aussehen:

Aufbau der Security-Umgebung

Plugin vs. Code

Jenkins bietet eine Vielzahl an Plugins zur Erweiterung der Funktionalitäten. Auch für Security-Tools wie den OWASP Dependency-Checker oder OWASP Zap gibt es einige Plugins. In der Theorie vereinfacht so ein Plugin den initialen Aufwand für die Einbindung der Tools – in der Praxis sind wir bisher immer derart schnell an Grenzen gestoßen, dass ohne eigenes Coding nicht wirklich viel funktioniert und die Einfachheit der Plugins dann eher störend wirkt. Um das zu vermeiden, bleiben wir bei der Variante “as-Code” und verwenden die declarative Pipeline von Jenkins mit ein paar eingebundenen Script-Blöcken. Letztere sind insbesondere für Kontrollstrukturen wie Schleifen oder den Umgang mit Rückgabewerten unumgänglich.

Ein Plugin verwenden wir dann aber doch: SSH Pipeline Steps u.a. mit dem Script-Befehl: sshCommand. Das wäre nicht unbedingt notwendig, erleichtert aber die Zugriffe auf SSH und die Übertragung von Dateien ein wenig.

Die Implementierung

Soweit sind also die Rahmenbedingungen. Alle Beispiel-Code-Sniplets finden Sie in unserem git-Repo. Das ist Beispielcode für Demozwecke! Bitte nachdenken beim Verwenden und an die eigenen Bedürfnisse anpassen (Stichwort: Umgebungsvariablen, Credentials, Pfade, URLs). Die folgende Implementierung ist losgelöst von irgendwelchen Build-Prozessen und kann dadurch im ersten Schritt auch händisch ausgelöst werden, um einen Einblick in das Thema Sicherheit und die Ergebnisse der Tools zu erlangen.

KALI – Basics

Bevor wir anfangen, sollte KALI laufen. Die Einrichtung von KALI soll hier nicht Bestandteil des Artikels sein. SSLyze und OWASP Zap sollten installiert sein – hier gibt es schöne Anleitungen im Internet. Die API von OWASP Zap (API-Key, Listener) muss konfiguriert sein.

Für die SSH-Zugriffe vom Jenkins aus verwenden wir SSH-Public-Key-Authentifizierung. Am besten legt ihr einen neuen Benutzer an (#adduser jenkinsuser), verpasst ihm einen SSH-Key (#ssh-keygen -b 4096 -t rsa) und fügt den öffentlichen Schlüssel den authorized_keys hinzu. Den privaten Schlüssel benötigen wir anschließend auf dem Jenkins-System.

Jenkins – Basics

Um nicht am offenen Herzen zu operieren, erstellen wir hier eine neue Pipeline für jedes Security-Tool. Bitte vorher das Plugin SSH Pipeline Steps via Plugin-Manager von Jenkins installieren. Wer eine Test-Build-Pipeline hat kann natürlich die Schritte auch dort direkt in die laufenden Prozesse implementieren :-):

Jenkins-Pipeline-Erstellung

Die neue Pipeline bekommt noch eine sprechende kurze Beschreibung und dann springen wir direkt zum Pipeline-Code.

SSLyze – Pipeline

Fangen wir mit SSLyze an. SSLyze hat wenige Parameter, läuft schnell durch und eignet sich daher auch zum Experimentieren mit Scripten und Rückgabewerten ausgezeichnet.

In Verbindung mit sshCommand verwenden wir drei Stages:

  • startSSLyze (Startet SSLyze und wartet auf das Ende des Durchlaufs)
  • checkRunningSSLyze (Prüf hier nur einmalig, ob SSLyze wirklich fertig ist.)
  • getResultsSSLyze (Wertet die Ergebnisse von SSLyze aus und gibt sie an Jenkins zurück)

Alle drei Stages rufen per SSH ein Python-Script auf dem KALI-Linux System auf. Beispiel startSSLyze:

 stages {
 stage('startSSLyze') {
  steps {
   script { 
    def remote = [:]
    remote.name = 'ScanHost'
    remote.host = '127.0.0.1'
    remote.user = 'jenkinsuser'
    remote.identityFile = '/var/lib/jenkins/jenkinsscriptlets/jenkinsuser_key'
    remote.allowAnyHosts = true 
    def ret = sshCommand remote: remote, command: "/usr/bin/python scriptlets/runSSLyze.py --serverURL https://lab.bi-sec.de"
    print ret
   }
  }
}

Den vollständigen Code der Pipeline findet ihr hier in unserem git-Repo.

Auf der Gegenseite nimmt das Python-Script runSSLyze.py den Befehl entgegen und startet SSLyze:

import os
import argparse

args = argparse.ArgumentParser()
args.add_argument("--serverURL", "-s", default=False, help="serverURL", required=True)
currArgs = args.parse_args()

serverURL = currArgs.serverURL
serverURL = serverURL.replace("http://", "")
serverURL = serverURL.replace("https://", "")
serverURL = serverURL.replace("/", "")

executablePath = "/usr/local/bin/sslyze"
executionCommand = "--regular"
outputCommand = "--xml_out="
outFilePath = "./"
os.system(executablePath + " " + executionCommand + " " + serverURL + " " + outputCommand + "" + outFilePath + "/" + serverURL +".xml &")

Analog funktionieren die beiden anderen Stages aus Sicht von Jenkins. Ein kurzer Aufruf des Scripts per sshCommand auf dem Remote-Host und die Anzeige der Ergebnisse im Jenkins.

Unsere Beispiel-Implementierung gibt alle Ergebnisse von SSLyze in der Jenkins-Console aus. Diese wollen wir uns natürlich nicht nach jedem Build anschauen. Also brauchen wir einen Automatismus, der bei bestimmten Ergebnissen die Stage – oder die Pipeline – failen lässt. Wie genau die Kriterien aussehen muss leider jeweils im Kontext der Anwendung und der Organisation entschieden werden: Ein öffentlicher Webshop wird vermutlich TLS1.1 unterstützen, um möglichst vielen Kunden zur Verfügung zu stehen. Ein Austauschportal für vertrauliche Daten soll vermutlich nur über TLS1.2 und auswärts abrufbar sein.

Die Ergebnisse des Prototyps

Die ersten Ergebnisse können ernüchternd sein. Die Ausgabe von Werkzeugen wie SSLyze überlässt die Interpretation der Ergebnisse dem Menschen vor dem Computer – oder dem Script, welches die Interpretation des Menschen in Code beinhaltet. Typische Schwachstellen- oder Web-Scanner liefern immerhin oft Indikatoren wie ein Risiko und eine Confidence als Anhaltspunkte zur Beschreibung der Schwachstellen. Hier finden Sie im Beispiel-Script zu Owasp ZAP ein paar Ideen zur Auswertung.

SSLyze – Was sagt uns das jetzt?

SSLyze ist ein doch schlechtes Beispiel !? – nein. Eigentlich ist SSLyze ein gutes Beispiel. Die Interpretation des Ergebnisses liegt bei Ihnen. Unser Beispiel-Code soll ein paar Ideen liefern. Wir unterteilen 2 Kategorien von Befunden: ShowStopper und Warnings. Diese zählen wir und lassen ab einem bestimmten Wert die Stage failen:

SSLyze-Pipeline-Stage-fail

Die Infos dazu liefert das Python-Script. Wir haben beispielsweise alle Cipher Suites mit dem Betriebsmodus CBC als “Warning” markiert – und festgelegt, dass ab einer Gesamtanzahl von 3 Warnings die Stage failed – nicht aber der gesamte Build-Prozess:

SSLyze-Ergebnis-Interpretation

Natürlich gibt es noch viel mehr Beispiele für schlechte Cipher Suites – etwa wenn SHA-1 oder MD5 verwendet werden. Auch EXPort-Ciphers oder anonyme Cipher suites wären Kandidaten für die Showstopper-Kategorie. Für ein vollständiges Bild helfen etwa die aktuellen Empfehlungen vom BSI oder aus dem Mozilla-TLS-Konfigurator.

 Try and Error

Ein einfaches Beispiel kann zu vielen Diskussionen führen. Aber: Sie haben den Anfang geschafft. Der Vorteil einer separaten Pipeline zu Beginn liegt sicher auch darin, dass Sie viel experimentieren können. Je nach Reifegrad der Entwicklung und der Organisation kommen mehr oder weniger viele Befunde zustande. OWASP Zap liefert schnell auch für Webseiten mit nur wenigen Unterseiten hunderte Ergebnisse. Da ist für den Start das “ignonieren” der “informational”-Befunde sicher eine gute Idee. Das Pipeline-Beispiel für SSLyze funktioniert übrigens mit wenigen Anpassungen für OWASP Zap – und die Python-Scripte liegen im git-Repo. Wie versprochen kann der Python-Code auch ohne Einbindung in eine Pipeline verwendet werden. OWASP Zap liefert für unsere – zugegeben absichtlich schlecht entwickelten – Lab-Webseite beispielsweise über 1300 Befunde:

OWASP Zap - Auswertung

Was hier nicht als mindestens Medium gekennzeichnet ist wird im ersten Schritt ignoriert – auch wenn nur das eine Cross-Site-Scripting behoben ist, dann ist die Webseite schon ein Stückchen besser und sicherer. Unser Sample-Script lässt sich aber auch schnell erweitern, um etwa bestimmte Findings zu ignorieren oder Befunde zusammen zu fassen: Alles Schritt für Schritt.

Die nächsten Schritte

Ein Anfang ist gemacht. Mit der Integration weiterer Werkzeuge wie OpenVAS, Nikto und co. wird es in der Verarbeitung der Ergebnisse natürlich etwas komplizierter. Steht das Grundgerüst ein Mal, dann ist es aber im Grunde “nur noch” das Einbinden weiterer Tools, das Einsammeln und Ablegen Tool-Ausgaben, das Ausblenden von False-Positives und die Entscheidung: Was mache ich mit den Ergebnissen? Hier kann ein Experte im Bereich der Websicherheit hilfreich sein. Ansonsten helfen die bewährten Ansätze Try-and-Error und Step-by-Step.

Wer sich die Mühe nicht machen will, viele einzelne Werkzeuge einzubinden, der darf sich gerne unsere Dienstleistungen im Bereich DevSecOps mal näher anschauen. Wir bieten beispielsweise die Einbindung diverser Sicherheits-Tools von Burp Suite über Nikto, OWASP Zap und SSLyze als externen Service über wenige API-Aufrufe: Sie übergeben die URL, wir prüfen und geben die vorab gemeinsam definierten Werte zurück – ob vollständige Befunde und Logs oder nur die Zusammenfassung der klassifizierten Befunde für die Entscheidung: failed oder success?