Deserialisierungsangriffe - die unbefugte Manipulation von serialisierten Daten zur Ausführung von bösartigem Code - können Unternehmen schweren Schaden zufügen. Entwickler und Sicherheitsteams müssen proaktiv wichtige Schwachstellen identifizieren, die Aufschluss darüber geben, wie, wann und wo der Angriff erfolgte.
Ein aktuelles Beispiel für diese Bedrohung ist CVE-2023-34040, ein Deserialisierungs-Angriffsvektor in Spring für Apache Kafka, der zu RCE (Remotecodeausführung) führen kann. Die Verbreitung von Deserialisierungsschwachstellen wird in der OWASP-Top-10-Liste hervorgehoben, in der die "Deserialisierung nicht vertrauenswürdiger Daten" als eines der 10 kritischsten Sicherheitsrisiken für Webanwendungen aufgeführt ist.
Tools wie OPSWAT MetaDefender Core™ mit seiner SBOM-EngineSoftware Bill of Materials) sind für die Erkennung und Verhinderung von Deserialisierungsangriffen unerlässlich. Diese ermöglichen es Entwicklern, ihren Code effizient zu scannen und zu analysieren, um sicherzustellen, dass keine Schwachstellen übersehen werden.
In diesem Blog erörtern unsere Graduate Fellows die Details von CVE-2023-34040 und dessen Ausnutzung sowie die Frage, wie man Open-Source-Komponenten gegen ähnliche Bedrohungen schützen kann.
Über CVE-2023-34040
CVE-2023-34040 offenbart einen Deserialisierungs-Angriffsvektor in Spring für Apache Kafka, der ausgenutzt werden kann, wenn eine ungewöhnliche Konfiguration angewendet wird. Diese Schwachstelle ermöglicht es einem Angreifer, ein bösartiges serialisiertes Objekt in einem der Deserialisierungsausnahme-Datensatz-Header zu konstruieren, was zu einem RCE führen kann. Die Schwachstelle betrifft die Spring for Apache Kafka Versionen 2.8.1 bis 2.9.10 und 3.0.0 bis 3.0.9.
Im Einzelnen ist eine Anwendung/ein Verbraucher unter den folgenden spezifischen Konfigurationen und Bedingungen anfällig:
- Die ErrorHandlingDeserializer-Klasse ist nicht für den Schlüssel und/oder Wert des Datensatzes konfiguriert.
- Die Eigenschaften checkDeserExWhenKeyNull und/oder checkDeserExWhenValueNull des Konsumenten werden auf true gesetzt.
- Nicht vertrauenswürdige Quellen dürfen in einem Kafka-Thema veröffentlichen.
Apache Kafka
Apache Kafka, entwickelt von der Apache Software Foundation, ist eine verteilte Event-Streaming-Plattform zur Erfassung, Verarbeitung, Beantwortung und Weiterleitung von Echtzeit-Datenströmen aus verschiedenen Quellen, einschließlich Datenbanken, Sensoren und mobile Geräten.
So können beispielsweise Benachrichtigungen an Dienste gestreamt werden, die auf Kundenaktivitäten wie den Abschluss einer Produktbestellung oder eine Zahlung reagieren.
In Apache Kafka dient ein Ereignis - auch als Datensatz oder Nachricht bezeichnet - als Dateneinheit, die ein Ereignis in der Anwendung darstellt, wenn Daten gelesen oder geschrieben werden. Jedes Ereignis enthält einen Schlüssel, einen Wert, einen Zeitstempel und optionale Metadaten-Kopfzeilen.
Schlüssel-binär (kann null sein) | Wert-binär (kann null sein) | ||||
Komprimierungsart [keine, gzip, snappy, lz4, zstd] | |||||
Kopfzeilen (optional)
| |||||
Teilung + Versatz | |||||
Zeitstempel (System- oder Benutzereinstellung) |
Ereignisse werden dauerhaft gespeichert und in Topics organisiert. Client-Anwendungen, die Ereignisse an Kafka-Themen senden (schreiben), werden als Produzenten bezeichnet, während diejenigen, die Ereignisse abonnieren (lesen und verarbeiten), als Konsumenten bezeichnet werden.
Spring für Apache Kafka
Um Apache Kafka mit dem Spring-Ökosystem zu verbinden, können Entwickler Spring für Apache Kafka verwenden, was die Integration in Java-Anwendungen vereinfacht.
Spring for Apache Kafka bietet robuste Tools und APIs, die das Senden und Empfangen von Ereignissen mit Kafka vereinfachen und es Entwicklern ermöglichen, diese Aufgaben ohne umfangreiche und komplexe Programmierung zu erledigen.
Serialisierung und Deserialisierung
Die Serialisierung ist ein Mechanismus zur Umwandlung des Zustands eines Objekts in eine Zeichenfolge oder einen Bytestrom. Im Gegensatz dazu ist die Deserialisierung der umgekehrte Prozess, bei dem die serialisierten Daten wieder in ihr ursprüngliches Objekt oder ihre ursprüngliche Datenstruktur umgewandelt werden. Durch die Serialisierung können komplexe Daten so umgewandelt werden, dass sie in einer Datei gespeichert, über ein Netzwerk gesendet oder in einer Datenbank gespeichert werden können. Serialisierung und Deserialisierung sind für den Datenaustausch in verteilten Systemen unerlässlich und fördern die Kommunikation zwischen den verschiedenen Komponenten einer Softwareanwendung. In Java wird writeObject() für die Serialisierung und readObject() für die Deserialisierung verwendet.
Da die Deserialisierung die Umwandlung eines Bytestroms oder einer Zeichenkette in ein Objekt ermöglicht, kann eine unsachgemäße Handhabung oder das Fehlen einer ordnungsgemäßen Validierung der Eingabedaten zu einer erheblichen Sicherheitsschwachstelle führen, die möglicherweise einen RCE-Angriff zur Folge hat.
Schwachstellenanalyse
Gemäß der CVE-Beschreibung konfigurierten die OPSWAT checkDeserExWhenKeyNull und checkDeserExWhenValueNull auf true, um die Sicherheitslücke auszulösen. Durch das Senden eines Datensatzes mit einem leeren Schlüssel/Wert und die Durchführung einer detaillierten Analyse durch Debuggen des Verbrauchers, während er einen Kafka-Datensatz vom Produzenten empfängt, deckten unsere Stipendiaten den folgenden Arbeitsablauf während der Datensatzverarbeitung auf:
Schritt 1: Empfang von Datensätzen (Nachrichten)
Beim Empfang von Datensätzen ruft der Verbraucher die Methode invokeIfHaveRecords() auf, die dann die Methode invokeListener() aufruft, um einen registrierten Datensatz-Listener (eine mit der Annotation @KafkaListener annotierte Klasse) für die eigentliche Verarbeitung der Datensätze auszulösen.
Der invokeListener() ruft dann die Methode invokeOnMessage() auf.
Schritt 2: Überprüfung der Aufzeichnungen
In der Methode invokeOnMessage() werden mehrere Bedingungen anhand des Datensatzwertes und der Konfigurationseigenschaften ausgewertet, die anschließend den nächsten auszuführenden Schritt bestimmen.
Wenn ein Datensatz einen Null-Schlüssel oder -Wert hat und die Eigenschaften checkDeserExWhenKeyNull und/oder checkDeserExWhenValueNull ausdrücklich auf true gesetzt sind, wird die Methode checkDeser() aufgerufen, um den Datensatz zu untersuchen.
Schritt 3: Prüfen der Ausnahmen in den Kopfzeilen
In checkDesr() ruft der Verbraucher kontinuierlich getExceptionFromHeader() auf, um alle Ausnahmen aus den Metadaten des Datensatzes abzurufen, falls vorhanden, und speichert das Ergebnis in einer Variablen namens exception.
Schritt 4: Extrahieren von Ausnahmen aus den Kopfzeilen
Die Methode getExceptionFromHeader() dient dazu, eine Ausnahme aus dem Header eines Kafka-Datensatzes zu extrahieren und zurückzugeben. Sie ruft zunächst den Header des Datensatzes ab und erhält dann den Wert des Headers, der in einem Byte-Array gespeichert ist.
Anschließend wird das Byte-Array mit dem Wert des Headers zur weiteren Bearbeitung an die Methode byteArrayToDeserializationException() weitergeleitet.
Schritt 5: Daten deserialisieren
In der byteArrayToDeserializationException() wird die Funktion resolveClass() überschrieben, um die Deserialisierung nur auf erlaubte Klassen zu beschränken. Dieser Ansatz verhindert die Deserialisierung jeder Klasse, die nicht ausdrücklich erlaubt ist. Der Byte-Array-Wert des Headers kann innerhalb von byteArrayToDeserializationException() nur dann deserialisiert werden, wenn er die in resolveClass() festgelegte Bedingung erfüllt, die eine Deserialisierung ausschließlich für die Klasse DeserializationException erlaubt.
Die Klasse DeserializationException erweitert jedoch die Standardklasse Exception und enthält einen Konstruktor mit vier Parametern. Der letzte Parameter, cause, stellt die ursprüngliche Ausnahme dar, die die DeserializationException ausgelöst hat, z. B. eine IOException oder eine ClassNotFoundException.
Die Klasse Throwable dient als Oberklasse für alle Objekte, die in Java als Ausnahmen oder Fehler geworfen werden können. In der Programmiersprache Java können Klassen zur Behandlung von Ausnahmen wie Throwable, Exception und Error sicher deserialisiert werden. Wenn eine Ausnahme deserialisiert wird, erlaubt Java das Laden und Instanziieren der Throwable-Elternklassen mit weniger anspruchsvollen Prüfungen als bei regulären Klassen.
Wenn die serialisierten Daten einer bösartigen Klasse entsprechen, die von der übergeordneten Klasse Throwable erbt, können die Bedingungsprüfungen umgangen werden, wie der Workflow und die umfassende Analyse zeigen. Dies ermöglicht die Deserialisierung eines bösartigen Objekts, das schädlichen Code ausführen und möglicherweise zu einem RCE-Angriff führen kann.
Ausbeutung
Wie in der Analyse angegeben, müssen zur Ausnutzung dieser Schwachstelle bösartige Daten erzeugt werden, die über den Kafka-Header-Datensatz an den Verbraucher gesendet werden. Zunächst muss der Angreifer eine bösartige Klasse erstellen, die die Throwable-Klasse erweitert, und dann eine Gadget-Kette verwenden, um Remotecodeausführung zu erreichen. Gadgets sind ausnutzbare Codeschnipsel innerhalb der Anwendung. Durch Verkettung dieser Gadgets kann der Angreifer ein "Sink-Gadget" erreichen, das schädliche Aktionen auslöst.
Im Folgenden finden Sie eine bösartige Klasse, die zur Ausnutzung dieser Schwachstelle in Spring für Apache Kafka verwendet werden kann:
Als nächstes wird eine Instanz der bösartigen Klasse erstellt und als Argument an den Cause-Parameter im Konstruktor der DeserializationException-Klasse übergeben. Die Instanz der DeserializationException wird dann in einen Byte-Stream serialisiert, der anschließend als Wert im Header des bösartigen Kafka-Datensatzes verwendet wird.
Wenn es dem Angreifer gelingt, das Opfer dazu zu bringen, seinen bösartigen Produzenten zu verwenden, kann er die Kafka-Datensätze kontrollieren, die an den Verbraucher gesendet werden, und so eine Möglichkeit schaffen, das System zu kompromittieren.
Wenn der anfällige Verbraucher einen Kafka-Datensatz vom böswilligen Produzenten erhält, der Null-Schlüssel und -Werte sowie eine böswillige serialisierte Instanz im Datensatz-Header enthält, verarbeitet der Verbraucher den Datensatz, einschließlich des Deserialisierungsprozesses. Dies führt letztendlich zu einer Remotecodeausführung, die es dem Angreifer ermöglicht, das System zu kompromittieren.
Abschwächung von CVE-2023-34040 mit SBOM in MetaDefender Core
Um die mit CVE-2023-34040 verbundenen Risiken wirksam zu mindern, benötigen Unternehmen eine umfassende Lösung, die Transparenz und Kontrolle über ihre Open-Source-Komponenten bietet.
SBOM, eine grundlegende Technologie innerhalb von MetaDefender Core, bietet eine leistungsstarke Antwort. SBOM dient als umfassendes Inventar aller verwendeten Softwarekomponenten, Bibliotheken und Abhängigkeiten und ermöglicht es Unternehmen, ihre Open-Source-Komponenten proaktiv und effizient zu verfolgen, zu sichern und zu aktualisieren.
Mit SBOM können Sicherheitsteams:
- Schnelles Auffinden anfälliger Komponenten: Identifizieren Sie sofort die Open-Source-Komponenten, die von Deserialisierungsangriffen betroffen sind. So können Sie schnell handeln und die gefährdeten Bibliotheken entweder patchen oder ersetzen.
- Sorgen Sie für proaktive Patches und Updates: Überwachen Sie Open-Source-Komponenten kontinuierlich mit SBOM, um Sicherheitslücken bei der Deserialisierung zu vermeiden. SBOM kann veraltete oder unsichere Komponenten erkennen, was rechtzeitige Updates ermöglicht und die Anfälligkeit für Angriffe verringert.
- Einhaltung von Vorschriften und Berichterstattung: SBOM unterstützt Unternehmen bei der Erfüllung von Compliance-Anforderungen, da gesetzliche Rahmenbedingungen zunehmend Transparenz in Software-Lieferketten vorschreiben.
Abschließende Überlegungen
Schwachstellen bei der Deserialisierung stellen eine erhebliche Sicherheitsbedrohung dar, die zur Ausnutzung einer Vielzahl von Anwendungen genutzt werden kann. Diese Schwachstellen treten auf, wenn eine Anwendung bösartige Daten deserialisiert, wodurch Angreifer beliebigen Code ausführen oder auf sensible Informationen zugreifen können. Die Sicherheitslücke CVE-2023-34040 in Spring für Apache Kafka führt uns die Gefahren von Deserialisierungsangriffen eindringlich vor Augen.
Um Deserialisierungsangriffe zu verhindern, ist es wichtig, fortschrittliche Tools wie OPSWAT MetaDefender Core und seine SBOM-Technologie zu implementieren. Unternehmen können einen tiefen Einblick in ihre Software-Lieferkette gewinnen, das rechtzeitige Patchen von Schwachstellen sicherstellen und sich gegen die sich ständig weiterentwickelnde Bedrohungslandschaft schützen. Die proaktive Sicherung von Open-Source-Komponenten ist nicht nur eine bewährte Praxis, sondern eine Notwendigkeit, um moderne Systeme vor potenziellen Angriffen zu schützen.