Les attaques de désérialisation - la manipulation non autorisée de données sérialisées pour exécuter un code malveillant - peuvent causer de graves dommages aux entreprises. Les développeurs et les équipes de sécurité doivent identifier de manière proactive les principales vulnérabilités qui révèlent comment, quand et où l'attaque s'est produite.
Un exemple récent de cette menace est la CVE-2023-34040, un vecteur d'attaque de désérialisation dans Spring pour Apache Kafka qui peut conduire à une exécution de code à distance (RCE). En fait, la prévalence des vulnérabilités de désérialisation est soulignée par la liste Top 10 de l'OWASP, qui inclut "Deserialization of Untrusted Data" (désérialisation de données non fiables) comme l'un des 10 risques de sécurité les plus critiques pour les applications web.
Des outils comme OPSWAT MetaDefender Core™ avec son moteur SBOMSoftware Bill of Materials) sont essentiels pour détecter et prévenir les attaques par désérialisation. Ils permettent aux développeurs de scanner et d'analyser efficacement leur code, en veillant à ce qu'aucune vulnérabilité ne soit négligée.
Dans ce blog, nos diplômés discuteront des détails de CVE-2023-34040 et de son exploitation, ainsi que de la manière de sécuriser les composants open-source contre des menaces similaires.
À propos de CVE-2023-34040
CVE-2023-34040 révèle un vecteur d'attaque par désérialisation dans Spring pour Apache Kafka, qui peut être exploité lorsqu'une configuration inhabituelle est appliquée. Cette vulnérabilité permet à un attaquant de construire un objet sérialisé malveillant dans l'un des en-têtes d'enregistrement d'exception de désérialisation, ce qui peut entraîner un RCE. La vulnérabilité affecte les versions 2.8.1 à 2.9.10 et 3.0.0 à 3.0.9 de Spring for Apache Kafka.
Plus précisément, une application/consommateur est vulnérable dans les configurations et conditions spécifiques suivantes :
- La classe ErrorHandlingDeserializer n'est pas configurée pour la clé et/ou la valeur de l'enregistrement.
- Les propriétés checkDeserExWhenKeyNull et/ou checkDeserExWhenValueNull du consommateur sont définies sur true.
- Les sources non fiables sont autorisées à publier dans un sujet Kafka.
Apache Kafka
Apache Kafka, développé par l'Apache Software Foundation, est une plateforme distribuée de flux d'événements conçue pour capturer, traiter, répondre et acheminer des flux de données en temps réel provenant de diverses sources, notamment des bases de données, des capteurs et des appareils mobile .
Par exemple, il peut transmettre des notifications à des services qui réagissent aux activités des clients, telles que l'achat d'un produit ou un paiement.
Dans Apache Kafka, un événement - également appelé enregistrement ou message - est une unité de données qui représente une occurrence dans l'application chaque fois que des données sont lues ou écrites. Chaque événement comprend une clé, une valeur, un horodatage et des en-têtes de métadonnées optionnels.
Clé binaire (peut être nul) | Valeur binaire (peut être nulle) | ||||
Type de compression [none, gzip, snappy, lz4, zstd] | |||||
En-têtes (facultatif)
| |||||
Partition + décalage | |||||
Horodatage (défini par le système ou l'utilisateur) |
Les événements sont stockés durablement et organisés en thèmes. Les applications clientes qui envoient (écrivent) des événements dans les thèmes Kafka sont appelées producteurs, tandis que celles qui s'abonnent aux événements (les lisent et les traitent) sont appelées consommateurs.
Spring pour Apache Kafka
Pour connecter Apache Kafka à l'écosystème Spring, les développeurs peuvent utiliser Spring for Apache Kafka, qui simplifie l'intégration dans les applications Java.
Spring for Apache Kafka offre des outils et des API robustes qui simplifient le processus d'envoi et de réception d'événements avec Kafka, permettant aux développeurs d'accomplir ces tâches sans avoir recours à un codage étendu et complexe.
Sérialisation et désérialisation
La sérialisation est un mécanisme de conversion de l'état d'un objet en une chaîne de caractères ou un flux d'octets. En revanche, la désérialisation est le processus inverse, au cours duquel les données sérialisées sont reconverties en leur objet ou structure de données d'origine. La sérialisation permet de transformer des données complexes afin qu'elles puissent être enregistrées dans un fichier, envoyées sur un réseau ou stockées dans une base de données. La sérialisation et la désérialisation sont essentielles pour l'échange de données dans les systèmes distribués et favorisent la communication entre les différents composants d'une application logicielle. En Java, writeObject() est utilisé pour la sérialisation et readObject() est utilisé pour la désérialisation.
La désérialisation permettant de convertir un flux d'octets ou une chaîne de caractères en un objet, une mauvaise manipulation ou l'absence de validation correcte des données d'entrée peut entraîner une vulnérabilité importante en matière de sécurité, pouvant conduire à une attaque de type RCE.
Analyse de la vulnérabilité
Selon la description du CVE, les boursiers OPSWAT ont configuré checkDeserExWhenKeyNull et checkDeserExWhenValueNull à true afin de déclencher la vulnérabilité de sécurité. En envoyant un enregistrement avec une clé/valeur vide et en menant une analyse détaillée en déboguant le consommateur lorsqu'il reçoit un enregistrement Kafka du producteur, nos boursiers diplômés ont découvert le flux de travail suivant pendant le traitement de l'enregistrement :
Étape 1 : Réception des enregistrements (messages)
Lorsqu'il reçoit des enregistrements, le consommateur invoque la méthode invokeIfHaveRecords(), qui appelle ensuite la méthode invokeListener() pour déclencher un récepteur d'enregistrements enregistré (une classe annotée avec l'annotation @KafkaListener ) pour le traitement effectif des enregistrements.
La méthode invokeListener() invoque ensuite la méthode invokeOnMessage().
Étape 2 : Vérification des dossiers
Dans la méthode invokeOnMessage(), plusieurs conditions sont évaluées par rapport à la valeur de l'enregistrement et aux propriétés de configuration, qui déterminent ensuite l'étape suivante à exécuter.
Si un enregistrement a une clé ou une valeur nulle et que les propriétés checkDeserExWhenKeyNull et/ou checkDeserExWhenValueNull sont explicitement définies à true, la méthode checkDeser() sera appelée pour examiner l'enregistrement.
Étape 3 : Vérification de l'exception dans les en-têtes
Dans checkDesr(), le consommateur invoque en permanence getExceptionFromHeader() pour extraire les exceptions des métadonnées de l'enregistrement, si elles sont présentes, et stocke le résultat dans une variable appelée exception.
Étape 4 : Extraction de l'exception à partir des en-têtes
La méthode getExceptionFromHeader() est conçue pour extraire et renvoyer une exception à partir de l'en-tête d'un enregistrement Kafka. Elle récupère d'abord l'en-tête de l'enregistrement, puis la valeur de l'en-tête, qui est stockée dans un tableau d'octets.
Ensuite, il transmet le tableau d'octets de la valeur de l'en-tête à la méthode byteArrayToDeserializationException() pour traitement ultérieur.
Étape 5 : Désérialisation des données
Dans l'exception byteArrayToDeserializationException(), la fonction resolveClass() est surchargée pour restreindre la désérialisation aux seules classes autorisées. Cette approche empêche la désérialisation de toute classe qui n'est pas explicitement autorisée. La valeur du tableau d'octets de l'en-tête ne peut être désérialisée dans byteArrayToDeserializationException() que si elle remplit la condition fixée dans resolveClass(), qui autorise la désérialisation exclusivement pour la classe DeserializationException.
Cependant, la classe DeserializationException étend la classe Exception standard et comprend un constructeur avec quatre paramètres. Le dernier paramètre, cause, représente l'exception originale qui a déclenché l'exception de désérialisation, telle qu'une exception IO ou une exception ClassNotFound.
La classe Throwable sert de superclasse pour tous les objets qui peuvent être lancés comme des exceptions ou des erreurs en Java. Dans le langage de programmation Java, les classes de gestion des exceptions telles que Throwable, Exception et Error peuvent être désérialisées en toute sécurité. Lorsqu'une exception est désérialisée, Java permet au parent Throwable des classes d'être chargé et instancié avec des contrôles moins exigeants que ceux appliqués aux classes normales.
Sur la base du flux de travail et d'une analyse complète, si les données sérialisées correspondent à une classe malveillante qui hérite de la classe parentale Throwable, elles peuvent contourner les contrôles de condition. Cela permet la désérialisation d'un objet malveillant, qui peut exécuter un code nuisible et potentiellement entraîner une attaque RCE.
Exploitation
Comme indiqué dans l'analyse, l'exploitation de cette vulnérabilité nécessite la génération de données malveillantes envoyées au consommateur via l'enregistrement d'en-tête Kafka. Dans un premier temps, l'attaquant doit créer une classe malveillante qui étend la classe Throwable, puis utiliser une chaîne de gadgets pour réaliser une exécution de code à distance. Les gadgets sont des extraits de code exploitables au sein de l'application, et en les enchaînant, l'attaquant peut atteindre un "gadget d'évier" qui déclenche des actions nuisibles.
Ce qui suit est une classe malveillante qui peut être utilisée pour exploiter cette vulnérabilité dans Spring pour Apache Kafka :
Ensuite, une instance de la classe malveillante est créée et transmise comme argument au paramètre cause dans le constructeur de la classe DeserializationException. L'instance de DeserializationException est ensuite sérialisée en un flux d'octets, qui est ensuite utilisé comme valeur dans l'en-tête de l'enregistrement Kafka malveillant.
Si l'attaquant réussit à tromper la victime en lui faisant utiliser son producteur malveillant, il peut contrôler les enregistrements Kafka envoyés au consommateur, ce qui lui donne la possibilité de compromettre le système.
Lorsque le consommateur vulnérable reçoit un enregistrement Kafka du producteur malveillant contenant des clés et des valeurs nulles, ainsi qu'une instance sérialisée malveillante dans l'en-tête de l'enregistrement, le consommateur traite l'enregistrement, y compris le processus de désérialisation. Cela conduit finalement à l'exécution de code à distance, permettant à l'attaquant de compromettre le système.
Atténuation de CVE-2023-34040 avec SBOM dans MetaDefender Core
Pour réduire efficacement les risques associés à CVE-2023-34040, les entreprises ont besoin d'une solution complète qui offre une visibilité et un contrôle sur leurs composants open-source.
SBOM, une technologie fondamentale de MetaDefender Core, apporte une réponse puissante. En agissant comme un inventaire complet de tous les composants logiciels, bibliothèques et dépendances utilisés, SBOM permet aux organisations de suivre, sécuriser et mettre à jour leurs composants open-source de manière proactive et efficace.
Avec le SBOM, les équipes de sécurité peuvent :
- Localiser rapidement les composants vulnérables : Identifier immédiatement les composants open-source affectés par les attaques de désérialisation. Cela permet d'agir rapidement en apportant des correctifs ou en remplaçant les bibliothèques vulnérables.
- Assurer des correctifs et des mises à jour proactifs : Surveillez en permanence les composants open-source grâce à SBOM afin de rester à l'affût des vulnérabilités liées à la désérialisation. Le SBOM peut détecter les composants obsolètes ou peu sûrs, ce qui permet d'effectuer des mises à jour en temps voulu et de réduire l'exposition aux attaques.
- Maintenir la conformité et les rapports : Le SBOM aide les organisations à répondre aux exigences de conformité, car les cadres réglementaires imposent de plus en plus de transparence dans les chaînes d'approvisionnement en logiciels.
Réflexions finales
Les vulnérabilités liées à la désérialisation constituent une menace importante pour la sécurité qui peut être utilisée pour exploiter un large éventail d'applications. Ces vulnérabilités se produisent lorsqu'une application désérialise des données malveillantes, ce qui permet aux attaquants d'exécuter du code arbitraire ou d'accéder à des informations sensibles. La vulnérabilité CVE-2023-34040 dans Spring pour Apache Kafka est un rappel brutal des dangers des attaques par désérialisation.
Pour prévenir les attaques par désérialisation, il est essentiel de mettre en œuvre des outils avancés tels qu'OPSWAT MetaDefender Core et sa technologie SBOM. Les organisations peuvent ainsi obtenir une visibilité approfondie de leur chaîne d'approvisionnement en logiciels, s'assurer que les vulnérabilités sont corrigées en temps voulu et se protéger contre un paysage de menaces en constante évolution. La sécurisation proactive des composants open-source n'est pas seulement une bonne pratique, c'est une nécessité pour protéger les systèmes modernes contre une exploitation potentielle.