Dieses Tutorial ist eines von vier, die Konzepte aus „Microservices March 2022“ in die Praxis umsetzen: Kubernetes-Netzwerk :
Möchten Sie detaillierte Anleitungen zur Verwendung von NGINX für noch mehr Anwendungsfälle von Kubernetes-Netzwerken? Laden Sie unser kostenloses E-Book „Verwaltung des Kubernetes-Verkehrs mit NGINX“ herunter: Ein praktischer Leitfaden .
Sie arbeiten in der IT-Abteilung eines beliebten Ladens vor Ort, der verschiedene Waren verkauft, von Kissen bis zu Fahrrädern. Sie stehen kurz vor der Eröffnung ihres ersten Online-Shops, haben jedoch einen Sicherheitsexperten gebeten, die Site vor der Veröffentlichung einem Penetrationstest zu unterziehen. Leider hat der Sicherheitsexperte ein Problem gefunden! Der Online-Shop ist anfällig für SQL-Injection . Der Sicherheitsexperte konnte die Site ausnutzen, um vertrauliche Informationen aus Ihrer Datenbank zu erhalten, darunter Benutzernamen und Passwörter.
Ihr Team ist zu Ihnen – dem Kubernetes-Ingenieur – gekommen, um die Situation zu retten. Glücklicherweise wissen Sie, dass SQL-Injection (sowie andere Schwachstellen) mithilfe von Kubernetes-Verkehrsverwaltungstools gemindert werden können. Sie haben bereits einen Ingress-Controller bereitgestellt, um die App verfügbar zu machen, und können in einer einzigen Konfiguration sicherstellen, dass die Sicherheitsanfälligkeit nicht ausgenutzt werden kann. Nun kann der Online-Shop pünktlich an den Start gehen. Gut gemacht!
Dieser Blog begleitet das Labor für Einheit 3 von Microservices März 2022 – Microservices-Sicherheitsmuster in Kubernetes und zeigt, wie NGINX und NGINX Ingress Controller verwendet werden, um SQL-Injection zu blockieren.
Zum Ausführen des Lernprogramms benötigen Sie einen Computer mit:
Um den größtmöglichen Nutzen aus dem Labor und dem Lernprogramm zu ziehen, empfehlen wir Ihnen, vor dem Beginn Folgendes zu tun:
In diesem Tutorial werden die folgenden Technologien verwendet:
Die Anweisungen für jede Herausforderung enthalten den vollständigen Text der YAML-Dateien, die zum Konfigurieren der Apps verwendet werden. Sie können den Text auch aus unserem GitHub-Repository kopieren. Zusammen mit dem Text jeder YAML-Datei wird ein Link zu GitHub bereitgestellt.
Dieses Tutorial umfasst vier Herausforderungen:
In dieser Herausforderung stellen Sie einen Minikube-Cluster bereit und installieren Podinfo als Beispiel-App, die Sicherheitslücken aufweist.
Stellen Sie einen Minikube- Cluster bereit. Nach einigen Sekunden bestätigt eine Meldung, dass die Bereitstellung erfolgreich war.
$ minikube start 🏄 Fertig! kubectl ist jetzt so konfiguriert, dass es standardmäßig den Cluster „minikube“ und den Namespace „default“ verwendet
Hier stellen Sie eine einfache E‑Commerce‑App bereit, die aus zwei Microservices besteht:
Führen Sie diese Schritte aus:
Erstellen Sie mit einem Texteditor Ihrer Wahl eine YAML-Datei namens 1-app.yaml mit dem folgenden Inhalt (oder kopieren Sie sie von GitHub ).
API-Version: Apps/v1 Art: Bereitstellung
Metadaten:
Name: App
Spezifikation:
Selektor:
MatchLabels:
App: App
Vorlage:
Metadaten:
Labels:
App: App
Spezifikation:
Container:
- Name: App
Image: f5devcentral/microservicesmarch:1.0.3
Ports:
- ContainerPort: 80
Umgebung:
- Name: MYSQL_USER
Wert: dan
- Name: MYSQL_PASSWORD
Wert: dan
- Name: MYSQL_DATABASE
Wert: sqlitraining
- Name: DATABASE_HOSTNAME
Wert: db.default.svc.cluster.local
---
API-Version: v1
Art: Dienst
Metadaten:
Name: App
Spezifikation:
Ports:
- Port: 80
ZielPort: 80
KnotenPort: 30001
Selektor:
App: App
Typ: NodePort
---
API-Version: Apps/v1
Art: Bereitstellung
Metadaten:
Name: db
Spezifikation:
Selektor:
MatchLabels:
App: db
Vorlage:
Metadaten:
Labels:
App: db
Spezifikation:
Container:
-Name: db
Image: mariadb:10.3.32-focal
Ports:
-ContainerPort: 3306
Umgebung:
- Name: MYSQL_ROOT_PASSWORD
Wert: root
- Name: MYSQL_USER
Wert: dan
- Name: MYSQL_PASSWORD
Wert: dan
- Name: MYSQL_DATABASE
Wert: sqlitraining
---
API-Version: v1
Art: Dienst
Metadaten:
Name: db
Spezifikation:
Ports:
-Port: 3306
ZielPort: 3306
Selektor:
App: db
Stellen Sie die App und die API bereit:
$ kubectl apply -f 1-app.yaml deployment.apps/app erstellt service/app erstellt deployment.apps/db erstellt service/db erstellt
Bestätigen Sie, dass die Podinfo-Pods bereitgestellt wurden, wie durch den Wert „Ausführen“
in der Spalte „STATUS“
angezeigt. Die vollständige Bereitstellung kann 30–40 Sekunden dauern. Warten Sie daher, bis der Status beider Pods „ Ausführen“ lautet,
bevor Sie mit dem nächsten Schritt fortfahren (und den Befehl bei Bedarf erneut eingeben).
$ kubectl get pods NAME BEREIT STATUS NEUSTART ALTER app-d65d9b879-b65f2 1/1 Läuft 0 37s db-7bbcdc75c-q2kt5 1/1 Läuft 0 37s
Öffnen Sie die App in Ihrem Browser:
$ minikube service app |-----------|------|-------------|--------------| | NAMESPACE | NAME | ZIELPORT | URL | |-----------|------|-------------|--------------| | Standard | App | | Kein Knotenport | |--------------|------|-------------|--------------| 😿 Service Standard/App hat keinen Knotenport 🏃 Tunnel für Service-App wird gestartet. |-----------|------|-------------|------------------------| | NAMESPACE | NAME | ZIELPORT | URL | |-----------|------|-------------|------------------------| | Standard | App | | http://127.0.0.1:55446 | |-----------|------|-------------|-------------| 🎉 Service Standard/App wird im Standardbrowser geöffnet …
Die Beispielanwendung ist eher einfach. Es umfasst eine Homepage mit einer Artikelliste (z. B. Kissen) und eine Reihe von Produktseiten mit Details wie einer Beschreibung und dem Preis. Die Daten werden in der MariaDB-Datenbank gespeichert. Bei jeder Seitenanforderung wird eine SQL-Abfrage an die Datenbank gesendet.
Wenn Sie die Produktseite der Kissen öffnen, bemerken Sie möglicherweise, dass die URL auf /product/1 endet. Der1 ist die ID des Produkts. Um das direkte Einfügen von Schadcode in die SQL-Abfrage zu verhindern, empfiehlt es sich, Benutzereingaben zu bereinigen, bevor Anfragen an Backend-Dienste weitergeleitet werden. Aber was passiert, wenn die App nicht richtig konfiguriert ist und die Eingabe nicht maskiert wird, bevor sie in die SQL-Abfrage an die Datenbank eingefügt wird?
Um herauszufinden, ob die App Eingaben ordnungsgemäß maskiert, führen Sie ein einfaches Experiment durch, indem Sie die ID in eine ändern, die in der Datenbank nicht vorhanden ist.
Ändern Sie das letzte Element in der URL manuell von1 Zu-1 . Die Fehlermeldung „Ungültige
Produkt
-ID
„-1““
zeigt an, dass die Produkt-ID nicht maskiert wird. Stattdessen wird die Zeichenfolge direkt in die Abfrage eingefügt. Das ist nicht gut, es sei denn, Sie sind ein Hacker!
Angenommen, die Datenbankabfrage lautet etwa:
WÄHLEN SIE * AUS EINER_TABELLE, WO ID = "1"
Um die Sicherheitslücke auszunutzen, die durch das Nicht-Entkommen der Eingabe entsteht, ersetzen Sie 1
mit -1"
<böswillige_Abfrage>
--
//
so dass:
"
) nach-1
schließt die erste Abfrage ab.--
//
verwirft den Rest der Abfrage.Wenn Sie beispielsweise das letzte Element in der URL in -1"
ändern oder 1
--
//
, die Abfrage wird wie folgt kompiliert:
SELECT * FROM some_table WHERE id = "-1" OR 1 -- //" -------------- ^ injiziert ^
Dadurch werden alle Zeilen aus der Datenbank ausgewählt, was bei einem Hack nützlich ist. Um herauszufinden, ob dies der Fall ist, ändern Sie die URL-Endung in ‑1"
. Die resultierende Fehlermeldung gibt Ihnen weitere nützliche Informationen über die Datenbank:
Schwerwiegender Fehler: Nicht abgefangene mysqli_sql_exception: Sie haben einen Fehler in Ihrer SQL-Syntax. Überprüfen Sie das Handbuch, das Ihrer MariaDB-Serverversion entspricht, auf die richtige Syntax, die in der Nähe von „-1“ in Zeile 1 in /var/www/html/product.php:23 verwendet werden muss. Stacktrace: #0 /var/www/html/product.php(23): mysqli->query('SELECT * FROM p...') #1 {main} geworfen in /var/www/html/product.php in Zeile 23
Jetzt können Sie mit der Manipulation des eingefügten Codes beginnen, um zu versuchen, die Datenbankergebnisse nach ID zu sortieren:
-1" ODER 1 ORDER BY id DESC -- //
Das Ergebnis ist die Produktseite für den letzten Artikel in der Datenbank.
Das Erzwingen einer Sortierung der Ergebnisse durch die Datenbank ist interessant, aber nicht besonders nützlich, wenn Ihr Ziel das Hacken ist. Der Versuch, Benutzernamen und Passwörter aus der Datenbank zu extrahieren, ist wesentlich lohnender.
Man kann davon ausgehen, dass in der Datenbank eine Benutzertabelle mit Benutzernamen und Passwörtern vorhanden ist. Aber wie erweitern Sie Ihren Zugriff von der Produkttabelle auf die Benutzertabelle?
Die Antwort besteht in der Einfügen von Code wie diesem:
-1" UNION SELECT * FROM Benutzer -- //
Wo
-1“
erzwingt die Rückgabe einer leeren Menge aus der ersten Abfrage.UNION
führt die Zusammenführung zweier Datenbanktabellen durch – in diesem Fall Produkte und Benutzer –, wodurch Sie Informationen (Passwörter) abrufen können, die in der ursprünglichen Tabelle ( Produkte ) nicht enthalten sind.SELECT
*
FROM
users
wählt alle Zeilen in der Benutzertabelle aus.--
//
verwirft alles nach der bösartigen Abfrage.Wenn Sie die URL so ändern, dass sie mit dem eingefügten Code endet, erhalten Sie eine neue Fehlermeldung:
Schwerwiegender Fehler: Nicht abgefangene mysqli_sql_exception: Die verwendeten SELECT-Anweisungen haben eine unterschiedliche Anzahl von Spalten in /var/www/html/product.php:23. Stacktrace: #0 /var/www/html/product.php(23): mysqli->query('SELECT * FROM p...') #1 {main} geworfen in /var/www/html/product.php in Zeile 23.
Diese Meldung zeigt an, dass die Tabellen „Produkte“ und „Benutzer“ nicht die gleiche Anzahl Spalten haben und der UNION
-Befehl daher nicht ausgeführt werden kann. Sie können die Anzahl der Spalten jedoch durch Ausprobieren ermitteln, indem Sie der SELECT-
Anweisung nacheinander die Spalten (Feldnamen) als Parameter hinzufügen. Ein guter Tipp für den Feldnamen in einer Benutzertabelle ist password
. Versuchen Sie also Folgendes:
# wähle 1 Spalte -1" UNION SELECT password FROM users; -- // # wähle 2 Spalten -1" UNION SELECT password,password FROM users; -- // # wähle 3 Spalten -1" UNION SELECT password,password,password FROM users; -- / # wähle 4 Spalten -1" UNION SELECT password,password,password,password FROM users; -- // # wähle 5 Spalten -1" UNION SELECT password,password,password,password,password FROM users; -- //
Die letzte Abfrage ist erfolgreich (und zeigt Ihnen an, dass die Benutzertabelle fünf Spalten enthält) und Sie sehen ein Benutzerkennwort:
Der zu diesem Passwort gehörende Benutzername ist Ihnen zu diesem Zeitpunkt noch nicht bekannt. Wenn Sie jedoch die Anzahl der Spalten in der Benutzertabelle kennen, können Sie dieselben Abfragetypen wie zuvor verwenden, um diese Informationen anzuzeigen. Nehmen Sie an, dass der relevante Feldname Benutzername
ist. Und das stellt sich als richtig heraus – die folgende Abfrage legt sowohl den Benutzernamen als auch das Passwort aus der Benutzertabelle offen. Das ist großartig – es sei denn, diese App wird auf Ihrer Infrastruktur gehostet!
-1" UNION SELECT Benutzername,Benutzername,Passwort,Passwort,Benutzername FROM Benutzer, wobei ID=1 -- //
Der Entwickler der Online-Shop-App muss natürlich mehr Wert auf die Bereinigung der Benutzereingaben legen (z. B. durch die Verwendung parametrisierter Abfragen), aber als Kubernetes-Ingenieur können Sie auch dazu beitragen, SQL-Injection zu verhindern, indem Sie den Angriff daran hindern, die App zu erreichen. Auf diese Weise fällt es nicht so sehr ins Gewicht, dass die App anfällig ist.
Es gibt viele Möglichkeiten, Ihre Apps zu schützen. Im weiteren Verlauf dieses Labors konzentrieren wir uns auf zwei Punkte:
Bei dieser Herausforderung fügen Sie einen Sidecar-Container in den Pod ein, um den gesamten Datenverkehr als Proxy zu verwenden und alle Anfragen abzulehnen, deren URL UNION
enthält.
Stellen Sie NGINX Open Source zunächst als Sidecar bereit und testen Sie dann, ob es bösartige Abfragen herausfiltert .
Notiz: Wir nutzen diese Technik nur zu Illustrationszwecken. In Wirklichkeit ist die manuelle Bereitstellung von Proxys als Sidecars nicht die beste Lösung (mehr dazu später).
Erstellen Sie eine YAML-Datei namens 2-app-sidecar.yaml mit dem folgenden Inhalt (oder kopieren Sie sie von GitHub ). Wichtige Aspekte der Konfiguration sind:
SELECT
oder UNION
enthält, wird abgelehnt (siehe den ersten Standortblock
im Abschnitt „ConfigMap“
).API-Version: Apps/v1
Art: Bereitstellung
Metadaten:
Name: App
Spezifikation:
Selektor:
MatchLabels:
App: App
Vorlage:
Metadaten:
Labels:
App: App
Spezifikation:
Container:
- Name: App
Image: f5devcentral/microservicesmarch:1.0.3
Ports:
- ContainerPort: 80
Umgebung:
- Name: MYSQL_USER
Wert: dan
- Name: MYSQL_PASSWORD
Wert: dan
- Name: MYSQL_DATABASE
Wert: sqlitraining
- Name: DATABASE_HOSTNAME
Wert: db.default.svc.cluster.local
- Name: Proxy # <-- Sidecar
Bild: „nginx“
Ports:
- ContainerPort: 8080
volumeMounts:
- mountPath: /etc/nginx
Name: nginx-config
volumes:
- Name: nginx-config
configMap:
Name: sidecar
---
apiVersion: v1
kind: Dienst
Metadaten:
Name: App
Spezifikation:
Ports:
- Port: 80
ZielPort: 8080 # <-- der Verkehr wird an den Proxy weitergeleitet
nodePort: 30001
Selektor:
App: App
Typ: NodePort
---
API-Version: v1
Art: ConfigMap
metadata:
name: sidecar
data:
nginx.conf: |-
events {}
http {
server {
listen 8080 default_server;
listen [::]:8080 default_server;
location ~* "(\'|\")(.*)(drop|insert|md5|select|union)" {
deny all;
}
location / {
proxy_pass http://localhost:80/;
}
}
}
---
apiVersion: apps/v1
kind: Bereitstellung
Metadaten:
Name: db
Spezifikation:
Selektor:
MatchLabels:
App: db
Vorlage:
Metadaten:
Labels:
App: db
Spezifikation:
Container:
-Name: db
Image: mariadb:10.3.32-focal
Ports:
-ContainerPort: 3306
Umgebung:
- Name: MYSQL_ROOT_PASSWORD
Wert: root
- Name: MYSQL_USER
Wert: dan
- Name: MYSQL_PASSWORD
Wert: dan
- Name: MYSQL_DATABASE
Wert: sqlitraining
---
API-Version: v1
Art: Dienst
Metadaten:
Name: db
Spezifikation:
Ports:
-Port: 3306
ZielPort: 3306
Selektor:
App: db
Den Beiwagen einsetzen:
$ kubectl apply -f 2-app-sidecar.yaml deployment.apps/app konfiguriert service/app konfiguriert configmap/sidecar erstellt deployment.apps/db unverändert service/db unverändert
Testen Sie, ob der Sidecar den Datenverkehr filtert, indem Sie zur App zurückkehren und die SQL-Injection erneut versuchen. NGINX blockiert die Anfrage, bevor sie die App erreicht!
-1" UNION SELECT Benutzername,Benutzername,Passwort,Passwort,Benutzername FROM Benutzer, wobei ID=1 -- //
Das Schützen Ihrer App wie in Herausforderung 3 ist aus pädagogischen Gründen interessant, für die Produktion empfehlen wir es jedoch aus folgenden Gründen nicht:
Eine viel bessere Lösung ist die Verwendung des NGINX Ingress Controllers, um den gleichen Schutz auf alle Ihre Apps auszuweiten! Mit Ingress-Controllern können alle Arten von Sicherheitsfunktionen zentralisiert werden, vom Blockieren von Anfragen wie bei einer Web Application Firewall (WAF) bis hin zur Authentifizierung und Autorisierung.
Bei dieser Herausforderung stellen Sie den NGINX Ingress Controller bereit , konfigurieren die Verkehrsweiterleitung und überprüfen, ob der Filter die SQL-Injection blockiert .
Am schnellsten lässt sich NGINX Ingress Controller mit Helm installieren.
Fügen Sie das NGINX-Repository zu Helm hinzu:
$ helm repo add nginx-stable https://helm.nginx.com/stable
Laden Sie den auf Open Source basierenden NGINX Ingress Controller herunter und installieren Sie ihn, der von F5 NGINX verwaltet wird. Beachten Sie den Parameter enableSnippets=true
: Snippets werden verwendet, um NGINX so zu konfigurieren, dass die SQL-Injection blockiert wird. Die letzte Ausgabezeile bestätigt die erfolgreiche Installation.
$ helm install main nginx-stable/nginx-ingress \ --set controller.watchIngressWithoutClass=true --set controller.service.type=NodePort \ --set controller.service.httpPort.nodePort=30005 \ --set controller.enableSnippets=true NAME: main LETZTE BEREITSTELLUNG: Tag Mo TT hh:mm:ss JJJJ NAMESPACE: Standard STATUS: bereitgestellt REVISION: 1 TESTSUITE: Keine ANMERKUNGEN: Der NGINX Ingress Controller wurde installiert.
Bestätigen Sie, dass der NGINX Ingress Controller-Pod bereitgestellt wurde, wie durch den Wert „Running“
in der Spalte „STATUS“
angezeigt.
$ kubectl get pods NAME BEREIT STATUS ... main-nginx-ingress-779b74bb8b-mtdkr 1/1 Wird ausgeführt ... ... STARTET DAS ALTER NEU ... 0 18 Sek.
Erstellen Sie eine YAML-Datei namens 3-ingress.yaml mit dem folgenden Inhalt (oder kopieren Sie sie von GitHub ). Es definiert das Ingress-Manifest, das zum Weiterleiten des Datenverkehrs an die App erforderlich ist (diesmal nicht über den Sidecar-Proxy). Beachten Sie den Block „Annotationen:“
, in dem ein Snippet verwendet wird, um die Konfiguration des NGINX Ingress Controllers mit demselben Standortblock
anzupassen wie in der ConfigMap-Definition in Herausforderung 3: Er lehnt alle Anfragen ab, die (neben anderen Zeichenfolgen) SELECT
oder UNION
enthalten.
API-Version: v1 Art: Dienst
Metadaten:
Name: App ohne Sidecar
Spezifikation:
Ports:
-Port: 80
ZielPort: 80
Selektor:
App: App
---
API-Version: networking.k8s.io/v1
Art: Ingress
metadata:
name: entry
annotations:
nginx.org/server-snippets: |
location ~* "(\'|\")(.*)(drop|insert|md5|select|union)" {
deny all;
}
spec:
ingressClassName: nginx
rules:
- host: "example.com"
http:
paths:
- backend:
service:
name: app-without-sidecar
port:
number: 80
Pfad: /
Pfadtyp: Präfix
$ kubectl apply -f 3-ingress.yaml service/app-without-sidecar erstellt ingress.networking.k8s.io/entry erstellt
Starten Sie einen verfügbaren BusyBox -Container, um eine Anforderung mit dem richtigen Hostnamen an den NGINX Ingress Controller-Pod zu senden.
$ kubectl run -ti --rm=true busybox --image=busybox $ wget --header="Host: example.com" -qO- main-nginx-ingress # …
Versuchen Sie die SQL-Injection. Der403
Der Statuscode „Verboten“
bestätigt, dass NGINX den Angriff blockiert!
$ wget --header="Host: example.com" -qO- 'main-nginx-ingress/product/-1"%20UNION%20SELECT%20username,username,password,password,username%20FROM%20users%20where%2 0id=1%20--%20//' wget: Server hat Fehler zurückgegeben: HTTP/1.1 403 Verboten
Kubernetes ist standardmäßig nicht sicher. Ein Ingress-Controller kann SQL-Injection-Schwachstellen (und viele andere) abschwächen. Bedenken Sie jedoch, dass die Art von WAF-ähnlicher Funktionalität, die Sie gerade mit NGINX Ingress Controller implementiert haben, weder ein echtes WAF noch die sichere Architektur von Apps ersetzt. Ein versierter Hacker kann den UNION-
Hack mit einigen kleinen Änderungen am Code dennoch zum Laufen bringen. Weitere Informationen zu diesem Thema finden Sie im A Pentester's Guide to SQL Injection (SQLi) .
Dennoch ist ein Ingress-Controller immer noch ein leistungsstarkes Tool zur Zentralisierung des Großteils Ihrer Sicherheit und führt zu mehr Effizienz und Sicherheit, einschließlich zentralisierter Authentifizierungs- und Autorisierungsanwendungsfälle (mTLS, Single Sign-On) und sogar eines robusten WAF wie F5 NGINX App Protect WAF .
Aufgrund der Komplexität Ihrer Apps und Architektur ist möglicherweise eine genauere Steuerung erforderlich. Wenn Ihre Organisation Zero Trust und End-to-End -Verschlüsselung benötigt, sollten Sie ein Service Mesh wie das immer kostenlose F5 NGINX Service Mesh in Betracht ziehen, um die Kommunikation zwischen Diensten im Kubernetes-Cluster (Ost-West-Verkehr) zu steuern. Wir untersuchen Service-Meshes in Einheit 4, Erweiterte Bereitstellungsstrategien für Kubernetes .
Ausführliche Informationen zum Bezug und zur Bereitstellung von NGINX Open Source finden Sie unter nginx.org .
Um den auf NGINX Plus basierenden NGINX Ingress Controller mit NGINX App Protect auszuprobieren, starten Sie noch heute Ihre kostenlose 30-Tage-Testversion oder kontaktieren Sie uns, um Ihre Anwendungsfälle zu besprechen .
Um den auf NGINX Open Source basierenden NGINX Ingress Controller auszuprobieren, sehen Sie sich die NGINX Ingress Controller Releases in unserem GitHub-Repo an oder laden Sie einen vorgefertigten Container von DockerHub herunter.
„Dieser Blogbeitrag kann auf Produkte verweisen, die nicht mehr verfügbar und/oder nicht mehr unterstützt werden. Die aktuellsten Informationen zu verfügbaren F5 NGINX-Produkten und -Lösungen finden Sie in unserer NGINX-Produktfamilie . NGINX ist jetzt Teil von F5. Alle vorherigen NGINX.com-Links werden auf ähnliche NGINX-Inhalte auf F5.com umgeleitet."