Core Rule Set als Teil von DevOps (CI Pipeline) – Teil 2

Im Blogpost vom 28. Februar 2019 erklärte Franziska Bühler, dass eine WAF früh in den Entwicklungsprozess eingebunden werden soll, um Produktionsprobleme durch frühzeitiges Testen zu vermeiden. In diesem Blogpost geht sie technisch in die Tiefe und zeigt auf, wie eine Continuous Integration Pipeline konkret umgesetzt werden kann.

Proof of Concept Setup

Für meinen PoC Setup verwende ich die absichtlich verwundbare Applikation Pixi des OWASP DevSlop Projekts. Als Web Application Firewall nutze ich ModSecurity mit dem OWASP Core Rule Set. Die Implementation der CI Pipeline habe ich dabei in CircleCI umgesetzt. Natürlich kommen hier auch TravisCI, Jenkins, GitLabCI etc. in Frage. Die End-To-End oder UI-Tests der Applikation Pixi habe ich in der Open Source Version von TestCafe geschrieben.

In einem herkömmlichen Test Setup werden die End-To-End Tests gegen die Applikation durchgeführt:

In einem nächsten Schritt starten wir das Core Rule Set als Reverse Proxy und stellen diesen vor die Applikation. Damit geht die ganze Kommunikation zur Applikation durch die WAF hindurch. Dieselben Tests werden nun erneut ausgeführt. Natürlich erwarten wir, dass die Tests auch mit dem CRS vor der Applikation immer noch erfolgreich durchgeführt werden können. Zusätzlich müssen die ModSecurity Logs leer sein. Dies bestätigt uns, dass die Tests keine Core Rule Set Regel und somit keinen False Positive getriggert haben.

Jeder dieser Komponenten läuft in einem Docker Container. Die benötigte Zeit, um den CRS Container zu pullen und zu starten, die Tests durch das CRS hindurch durchzuführen und die ModSecurity Logs auszuwerten, dauert nur 1 Minute und 30 Sekunden. Das bedeutet für uns: Wir verlieren nicht viel Zeit und gewinnen viel an zusätzlicher Sicherheit. Natürlich können diese Zeiten durch paralleles Ausführen der Tests mit und ohne WAF weiter optimiert werden.

Doch schauen wir uns jetzt die technische Umsetzung etwas genauer an.

CI Konfiguration

Der Output der Pipeline sieht dann so au:

Folgende Konfiguration zeigt einen Ausschnitt der .circleci/config.yml, die übrigens im GitHub Repository von DevSlop detailliert angeschaut werden kann.

# .circleci/config.yml 
version: 2 
jobs: 
   build: 
      docker: 
         - image: circleci/node:stretch 
      steps: 
          -run: 
             name: Install dependencies 
             ... 
         -run 
             name: Install Docker Compose 
             ... 
         -setup_remote_docker 
         -checkout 
         -run: 
             name: Start App Container 
             ... 
         -run: 
             name: Start OWASP ModSecurity CRS Container in front of application for application tests 
             #http://172.17.0.2:8001 
             #we set inbound and outbound anomaly score to 1, no tolerance 
             command: | 
                docker pull franbuehler/modsecurity-crs-rp && \ 
                docker run -dt --name apachecrstc -e PARANOIA=2 -e \ 
                ANOMALYIN=1 -e ANOMALYOUT=1 -e BACKEND=http://172.17.0.1:8000 \ 
                -e PORT=8001 --expose 8001 franbuehler/modsecurity-crs-rp 
         -run: 
             name: ModSecurity Tuning - Load rule exclusions 
             command: | 
                printf '# Rule 942450 (msg: SQL Hex Encoding Identified) triggers,\n' > tmp.conf 
                printf '# because of random characters in the session cookie.\n' >> tmp.conf 
                printf '\nSecRuleUpdateTargetById 942450 "!REQUEST_COOKIES:session"\n' >> tmp.conf 
                # CRS container for application tests: 
                docker cp tmp.conf apachecrstc:/etc/httpd/modsecurity.d/owasp-crs/rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf; 
                docker exec apachecrstc /usr/sbin/httpd -k graceful 
         - run: 
             name: Application Tests with Testcafe 
             command: | 
                # https://circleci.com/docs/2.0/building-docker-images/#mounting-folders 
                # creating dummy container which will hold a volume with config 
                docker create -v /tests --name configs alpine:latest /bin/true 
                # copying config file into this volume 
                docker cp /home/circleci/project/testcafe/tests/test.js configs:/tests 
                # starting application container using this volume 
                docker pull testcafe/testcafe 
                docker run --volumes-from configs:rw --name testcafe -it testcafe/testcafe 'chromium:headless --no-sandbox' /tests/test.js 
         - run: 
             name: Application Tests with CRS with Testcafe 
             command: | 
                docker cp /home/circleci/project/testcafe/tests/testcrs.js configs:/tests 
                docker run --volumes-from configs:rw --name testcafecrs -it testcafe/testcafe 'chromium:headless --no-sandbox' /tests/testcrs.js 
         - run: 
             name: WAF Tests with malicous request to test WAF itself 
             command: | 
                docker cp /home/circleci/project/testcafe/tests/testwaf.js configs:/tests 
                docker run --volumes-from configs:rw --name testcafewaf -it testcafe/testcafe 'chromium:headless --no-sandbox' /tests/testwaf.js 
         - run: 
             # Fail if ModSecurity log is not empty 
             name: Show ModSecurity logs of Testcafe Tests 
             ... 
         - run: 
             # Fail if ModSecurity log does not contain WAF Test String "My evil WAF Test" 
             # '<script>alert ("My evil WAF Test")</srcript>'
             name: Search for WAF Test String "My evil WAF Test" in ModSecurity logs
             command: |                 
             docker exec apachecrstc cat /var/log/apache2/error.log \
             | grep ModSecurity | grep "My evil WAF Test"         
             ...

Gehen wir nun zusammen die Schritte durch und schauen wir uns gemeinsam an, was durchgeführt wird:

Schritt „Spin up Environment“: Zuerst müssen wir CircleCI mitteilen, welches Image es für den Primary Docker Container verwenden soll. Innerhalb dieses Containers werden nun alle folgenden Schritte durchgeführt.

Schritte „Install dependencies“, „Install Docker Compose“ und „setup_remote_docker“: CircleCI soll für uns einige Abhängigkeiten, docker-compose und Docker installieren. Wir werden das später für unsere Container verwenden.

Schritt „checkout“: Die oben abgedruckte .circleci/config.yml Datei liegt im Root Ordner des Repository der Applikation. In diesem Schritt checkt CircleCI nun den Applikationscode aus, in welcher diese Datei .circleci/config.yml liegt.

Schritt „Start App Container“: CircleCI startet nun die Applikation Pixi in einem Docker Container.

Schritt „Start OWASP ModSecurity CRS Container in front of application for application tests“: Jetzt startet CircleCI den Core Rule Set Container als Reverse Proxy vor der Applikation Pixi, die wir im obigen Schritt gestartet haben.

Schritt „ModSecurity Tuning – Load rule exclusions“: In diesem Schritt haben wir die Möglichkeit ModSecurity CRS zu tunen. Das bedeutet, dass wir für einzelne Argumente eine CRS Regel ausschalten. Damit wollen wir einerseits False Positives vermeiden und andererseits das ModSecurity Log nicht unnötig füllen.

Schritt „Application Tests with Testcafe“: Die ersten Tests führen wir gegen den TCP Port des Applikationscontainers aus. Damit stellen wir sicher, dass die Tests und die Applikation funktionieren.
Wir wollen damit unterscheiden können, ob fehlgeschlagene Tests auf die Applikation oder fehlerhafte Tests auf False Positives des CRS zurückzuführen sind.

Schritt „Application Tests with CRS with Testcafe“: Die exakt selben Tests werden nun wiederholt, aber dieses Mal gegen den TCP Port des Core Rule Set Containers. Test Endpoint Konfiguration ist in den Dateien tests/test.js (Applikation) und tests/testcrs.js (WAF Reverse Proxy) unterschiedlich. Das bedeutet, dasss wir die Applikation durch die WAF hindurch testen. Diese Tests müssen ebenfalls erfolgreich sein.

Schritt „WAF Tests“: Natürlich möchten wir uns versichern, dass die WAF auch wirklich funktioniert. Dies tun wir, indem wir einen String übermitteln, von dem wir wissen, dass er mindestens eine CRS Regel triggern wird. In diesem Beispiel senden wir einen Cross Site Scripting (XSS) Payload an ein Suchfeld.

Schritt „Show ModSecurity logs of Testcafe Tests“: Am Schluss prüfen wir die Resultate. Das ModSecurity Log muss jetzt leer sein. Natürlich ignorieren wir unseren Testpayload vom obigen Schritt. Das bedeutet, dass unsere Tests keine CRS Regel getriggert haben.

Schritt „Search for WAF Test String ‚My evil WAF Test‘ in ModSecurity logs“: Wir suchen jetzt noch nach unserem XSS Test Payload um sicher zu sein, dass die WAF/das CRS richtig funktioniert. Falls der Payload im Log fehlen würde, würde dies bedeuten, dass die Tests nicht richtig funktionieren oder dass das CRS nicht richtig greift.

Im obigen Bild sehen wird, gekennzeichnet durch den roten Balken, dass die ModSecurity Logs der Tests nicht leer sind. Dieses Problem müssen wir lösen, bevor die Applikation in die Produktion gehen kann.
Nur wenn jeder dieser Schritte grün und somit erfolgreich ist, darf die Applikation released werden und in Produktion gehen.

Tipps zur Umsetzung in der Produktion

Dieser PoC deckt den Continuous Integration Teil ab. Natürlich kann dies auch auf Continuous Deployment ausgeweitet werden. Um den Prozess auszuweiten und mit dem Container in Produktion zu gehen, empfehle ich Folgendes:

  • Je mehr Tests, desto besser! Im Minimum sollte der Loginprozess getestet werden. Häufig treten False Positives im Zusammenhang mit Cookies auf, wenn beispielsweise Random Characters vorkommen.
  • Die Tests sollten in der Applikation herumklicken. Alle GET und POST Argumente sollten abgedeckt werden. Häufig treten auch False Positives in Argumenten auf.
  • Je umfangreicher die Applikation getestet wird, umso mehr potenzielle False Positives können erkannt werden.
  • Die Tests während des Entwicklungsprozess und während Continuous Integration sollten mit einem tiefen Anomaly Inbound und Outbound Threshold (Limite, ab wann die WAF greift) durchgeführt werden. Wir möchten jeden False Positive sofort erkennen!
  • Wenn trotz unserer Tests in der Produktion nun False Positives auftauchen sollten (zu wenige Tests?), dann sollte für jedes dieser Probleme ein neuer Test geschreiben werden. Dies versichert uns, dass der False Positive verschwindet.
  • Ein neues Feature innerhalb der Applikation sollte ebenfalls mit einem Test abgedeckt werden. Natürlich sollte das immer der Fall sein, aber umso mehr, wenn wir das CRS vor die Applikation stellen.
  • Möglicherweise muss der CRS Docker Container für die eigene Organisation angepasst werden. Mein Container ist ein Beispiel, wie es gemacht werden könnte.
  • Ich arbeite im Moment mit einem der Projektleiter des CRS daran, meine Änderungen von franbuehler/modsecurity-crs-rp in den offiziellen owasp/modsecurity-crs Container zu übernehmen. Das fehlende TLS ist eines der offenen Punkte, das neu sicher abgedeckt werden wird.

Präsentationen

Ich hielt auch Präsentationen zu diesem Thema, zB. an den DevOpsDays Zürich, Open Cloud Day in Zürich und dem Open Security Summit in London im 2018 und der DevOps Fusion in 2019 in Zürich. Die Slides können hier heruntergeladen werden: https://www.slideshare.net/franbuehler.

Referenzen

Die getestete Backend Applikation heisst Pixi und ist Teil des OWASP Projektes DevSlop, wo ich neben Tanya Janca, Nicole Becher und Nancy Gariché ein Teammember bin. Das OWASP DevSlop Projekt besteht aus verschiedenen Modulen und jedes davon lehrt DevSecOps über verschiedene Beispielpipelines, YouTube Shows, Blog Posts usw.

Die CircleCI Konfiguration und alle Testcafe Tests können auf GitHub unter der Organisation DevSlop gefunden werden: https://github.com/DevSlop/pixi-crs/

Mein CRS Docker Image kann hier gepulled werden: https://hub.docker.com/r/franbuehler/modsecurity-crs-rp

Zum Schluss möchte ich noch das Buch von Julien Vehent mit dem Titel „Securing DevOps – Security in the Cloud“ empfehlen.

Kommentare sind geschlossen.