Be Safe!
Im Zeitalter der Open-Source-Softwareprojekte sind Angriffe auf verwundbare Software allgegenwärtig. Python ist die beliebteste Sprache für Data Science und Engineering und wird daher zunehmend zum Ziel von Angriffen durch bösartige Bibliotheken. Außerdem können öffentlich zugängliche Anwendungen durch Angriffe auf Schwachstellen im Quellcode ausgenutzt werden.
Aus diesem Grund ist es wichtig, dass Dein Code keine CVEs (Common Vulnerabilities and Exposures) enthält oder andere Bibliotheken verwendet, die bösartig sein könnten. Das gilt besonders, wenn es sich um öffentlich zugängliche Software handelt, z. B. eine Webanwendung. Bei statworx suchen wir nach Möglichkeiten, die Qualität unseres Codes durch den Einsatz automatischer Scan-Tools zu verbessern. Deshalb besprechen wir den Wert von zwei Code- und Paketscannern für Python.
Automatische Überprüfung
Es gibt zahlreiche Tools zum Scannen von Code und seinen Abhängigkeiten. Hier werde ich einen Überblick über die beliebtesten Tools geben, die speziell für Python entwickelt wurden. Solche Tools fallen in eine von zwei Kategorien:
- Statische Anwendungssicherheitstests (SAST): suchen nach Schwachstellen im Code und verwundbaren Paketen
- Dynamische Anwendungssicherheitstests (DAST): suchen nach Schwachstellen, die während der Laufzeit auftreten
Im Folgenden werde ich bandit
und safety
anhand einer kleinen, von mir entwickelten streamlit
-Anwendung vergleichen. Beide Tools fallen in die Kategorie SAST, da sie die Anwendung nicht laufen lassen müssen, um ihre Prüfungen durchzuführen. Dynamische Anwendungstests sind komplizierter und werden vielleicht in einem späteren Beitrag behandelt.
Die Anwendung
Um den Zusammenhang zu verdeutlichen, hier eine kurze Beschreibung der Anwendung: Sie wurde entwickelt, um die Konvergenz (oder deren Fehlen) in den Stichprobenverteilungen von Zufallsvariablen zu visualisieren, die aus verschiedenen theoretischen Wahrscheinlichkeitsverteilungen gezogen wurden. Die Nutzer:innen können die Verteilung (z. B. Log-Normal) auswählen, die maximale Anzahl der Stichproben festlegen und verschiedene Stichprobenstatistiken (z. B. Mittelwert, Standardabweichung usw.) auswählen.
Bandit
Bandit ist ein quelloffener Python-Code-Scanner, der nach Schwachstellen im Deinem Code – und nur in Deinem Code – sucht. Er zerlegt den Code in seinen abstrakten Syntaxbaum und führt Plugins gegen diesen aus, um auf bekannte Schwachstellen zu prüfen. Neben anderen Tests prüft es einfachen SQL-Code, der eine Öffnung für SQL-Injektionen bieten könnte, im Code gespeicherte Passwörter und Hinweise auf häufige Angriffsmöglichkeiten wie die Verwendung der Bibliothek “Pickle”.
Bandit ist für die Verwendung mit CI/CD konzipiert und gibt einen Exit-Status von 1 aus, wenn es auf Probleme stößt, wodurch die Pipeline beendet wird. Es wird ein Bericht erstellt, der Informationen über die Anzahl der Probleme enthält, die nach Vertrauenswürdigkeit und Schweregrad in drei Stufen unterteilt sind: niedrig, mittel und hoch. In diesem Fall findet bandit
keine offensichtlichen Sicherheitslücken in unserem Code.
Run started:2022-06-10 07:07:25.344619
Test results:
No issues identified.
Code scanned:
Total lines of code: 0
Total lines skipped (#nosec): 0
Run metrics:
Total issues (by severity):
Undefined: 0
Low: 0
Medium: 0
High: 0
Total issues (by confidence):
Undefined: 0
Low: 0
Medium: 0
High: 0
Files skipped (0):
Umso wichtiger ist es, Bandit für die Verwendung in Deinem Projekt sorgfältig zu konfigurieren. Manchmal kann es eine Fehlermeldung auslösen, obwohl Du bereits weißt, dass dies zur Laufzeit kein Problem darstellen würde. Wenn Du zum Beispiel eine Reihe von Unit-Tests hast, die pytest
verwenden und als Teil Deiner CI/CD-Pipeline laufen, wird Bandit normalerweise eine Fehlermeldung auslösen, da dieser Code die assert
-Anweisung verwendet, die nicht für Code empfohlen wird, der nicht ohne das -O
-Flag läuft.
Um dieses Verhalten zu vermeiden, kannst Du:
1. Scans gegen alle Dateien durchführen, aber den Test über die Befehlszeilenschnittstelle ausschließen.
2. eine Konfigurationsdatei yaml
erstellen, um den Test auszuschließen.
Hier ist ein Beispiel:
# bandit_cfg.yml
skips: ["B101"] # skips the assert check
Dann können wir bandit wie folgt ausführen: bandit -c bandit_yml.cfg /path/to/python/files
und die unnötigen Warnungen werden nicht auftauchen.
Safety
Entwickelt vom Team von pyup.io
, läuft dieser Paketscanner gegen eine kuratierte Datenbank, die aus manuell überprüften Einträgen besteht, die auf öffentlich verfügbaren CVEs und Changelogs basieren. Das Paket ist für Python >= 3.5 verfügbar und kann kostenlos installiert werden. Standardmäßig verwendet es <a href="https://github.com/pyupio/safety-db">Safety DB</a>
, die frei zugänglich ist. Pyup.io bietet auch bezahlten Zugang zu einer häufiger aktualisierten Datenbank.
Die Ausführung von safety check --full-report -r requirements.txt
im Wurzelverzeichnis des Pakets gibt uns die folgende Ausgabe (aus Gründen der Lesbarkeit gekürzt):
+==============================================================================+
| |
| /$$$$$$ /$$ |
| /$$__ $$ | $$ |
| /$$$$$$$ /$$$$$$ | $$ \__//$$$$$$ /$$$$$$ /$$ /$$ |
| /$$_____/ |____ $$| $$$$ /$$__ $$|_ $$_/ | $$ | $$ |
| | $$$$$$ /$$$$$$$| $$_/ | $$$$$$$$ | $$ | $$ | $$ |
| \____ $$ /$$__ $$| $$ | $$_____/ | $$ /$$| $$ | $$ |
| /$$$$$$$/| $$$$$$$| $$ | $$$$$$$ | $$$$/| $$$$$$$ |
| |_______/ \_______/|__/ \_______/ \___/ \____ $$ |
| /$$ | $$ |
| | $$$$$$/ |
| by pyup.io \______/ |
| |
+==============================================================================+
| REPORT |
| checked 110 packages, using free DB (updated once a month) |
+============================+===========+==========================+==========+
| package | installed | affected | ID |
+============================+===========+==========================+==========+
| urllib3 | 1.26.4 | <1.26.5 | 43975 |
+==============================================================================+
| Urllib3 1.26.5 includes a fix for CVE-2021-33503: An issue was discovered in |
| urllib3 before 1.26.5. When provided with a URL containing many @ characters |
| in the authority component, the authority regular expression exhibits |
| catastrophic backtracking, causing a denial of service if a URL were passed |
| as a parameter or redirected to via an HTTP redirect. |
| https://github.com/advisories/GHSA-q2q7-5pp4-w6pg |
+==============================================================================+
Der Bericht enthält die Anzahl der überprüften Pakete, die Art der als Referenz verwendeten Datenbank und Informationen über jede gefundene Schwachstelle. In diesem Beispiel ist eine ältere Version des Pakets urllib3
von einer Schwachstelle betroffen, die technisch gesehen von einem Angreifer für einen Denial-of-Service-Angriff genutzt werden könnte.
Integration in den Workflow
Sowohl bandit
als auch safety
sind als GitHub Actions verfügbar. Die stabile Version von safety
bietet auch Integrationen für TravisCI und GitLab CI/CD.
Natürlich kannst Du beide Pakete immer manuell von PyPI auf Deinem Runner installieren, wenn keine fertige Integration wie eine GitHub Actions verfügbar ist. Da beide Programme von der Kommandozeile aus verwendet werden können, kannst Du sie auch lokal in einen Pre-Commit-Hook integrieren, wenn die Verwendung auf Deiner CI/CD-Plattform nicht in Frage kommt.
Die CI/CD-Pipeline für die obige Anwendung wurde mit GitHub Actions erstellt. Nach der Installation der erforderlichen Pakete der Anwendung wird zuerst bandit
und dann safety
ausgeführt, um alle Pakete zu scannen. Wenn alle Pakete aktualisiert sind, werden die Schwachstellen-Scans bestanden und das Docker-Image wird erstellt.
Package check | Code Check |
---|---|
Fazit
Ich würde dringend empfehlen, sowohl bandit
als auch safety
in Deiner CI/CD-Pipeline zu verwenden, da sie Sicherheitsüberprüfungen für Deinen Code und Deine Abhängigkeiten bieten. Bei modernen Anwendungen ist die manuelle Überprüfung jedes einzelnen Pakets, von dem Deine Anwendung abhängt, einfach nicht machbar, ganz zu schweigen von all den Abhängigkeiten, die diese Pakete haben! Daher ist automatisiertes Scannen unumgänglich, wenn Du ein gewisses Maß an Bewusstsein darüber haben willst, wie unsicher Dein Code ist.
Während bandit
Deinen Code auf bekannte Exploits untersucht, prüft es keine der in Deinem Projekt verwendeten Bibliotheken. Hierfür benötigen Sie safety
, da es die bekannten Sicherheitslücken in den Bibliotheken, von denen die Anwendung abhängt, aufzeigt. Zwar sind beide Frameworks nicht völlig idiotensicher, aber es ist immer noch besser, über einige CVEs informiert zu werden als über gar keine. Auf diese Weise kannst Du entweder Deinen anfälligen Code korrigieren oder eine anfällige Paketabhängigkeit auf eine sicherere Version aktualisieren.
Wenn Du Deinen Code sicher und Deine Abhängigkeiten vertrauenswürdig hältst, kannst Du potenziell verheerende Angriffe auf Deine Anwendung abwehren.