ClickCease Les bogues derrière les vulnérabilités Partie 3

Table des matières

Rejoignez notre populaire bulletin d'information

Rejoignez 4 500+ professionnels de Linux et de l'Open Source !

2 fois par mois. Pas de spam.

Les bogues derrière les vulnérabilités Partie 3

Joao Correia

3 janvier 2023 - Évangéliste technique

Voici la troisième partie de notre série de blogs en cinq parties consacrée à l'exploration des bogues de code à l'origine des vulnérabilités qui apparaissent chaque jour. Dans cette partie, nous couvrirons les bogues n°15 à n°11 du Mitre CWE Top 25 de Mitre pour 2022, y compris le favori de tous, le "déréférencement du pointeur NULL", au numéro 11.

Vous pouvez trouver la partie 1 ici et la partie 2 ici.

15 - Utilisation d'identifiants codés en dur

Les problèmes classés dans cette classe de vulnérabilité proviennent de l'inclusion d'informations d'identification dans un paquet compilé, ou de leur inclusion dans le support de distribution ou le dispositif de stockage qui atteint l'utilisateur final. Cela signifie que, si elles étaient découvertes, ces informations d'identification fonctionneraient sur tous les mêmes appareils. Plutôt qu'un bogue de codage en soi, il s'agit plutôt d'un problème de conditionnement et de diffusion.

Cela peut se produire de plusieurs façons - comme l'utilisation de d'identifiants de test codés en dur pendant le CI/CD qui atteignent le livrable à la fin du processus (par opposition à l'utilisation de jetons d'authentification dédiés, par exemple), ou l'inclusion d'une forme de porte dérobée sur un dispositif de production qui arrive sur le marché.

Une fois découvert, ce type de problème peut être très difficile à résoudre. Lorsqu'il se produit dans un dispositif matériel, la seule façon de supprimer un code d'identification codé en dur est de mettre à jour le micrologiciel. La difficulté de cette opération dépend du dispositif en question.

Mais cela peut se produire, et se produit, sur des logiciels aussi, et pas seulement sur du matériel. Dans ce cas, comme les informations d'identification sont intégrées dans le logiciel, une mise à jour de la version est généralement nécessaire pour résoudre le problème.

Il est également relativement facile de repérer ces types d'informations d'identification lors de la rétroconception de logiciels ou de matériels, de sorte que les acteurs malveillants aiment beaucoup les découvrir au cours de leurs opérations.

Le véritable problème des identifiants codés en dur est qu'ils annulent toutes les restrictions d'accès que vous pouvez mettre en place dans un système donné. Si un identifiant d'administrateur codé en dur existe, peu importe que vous n'ayez pas créé d'autres comptes administratifs - l'identifiant codé en dur pourra toujours entrer.

Au niveau architectural, les informations d'identification codées en dur apparaissent également lors de la configuration des connexions entre les systèmes, où une chaîne de connexion ou un mot de passe sont stockés dans un fichier de configuration qui est lu pour établir la connexion. Un attaquant qui trouverait ce fichier serait en mesure d'usurper l'identité de la connexion.

14 - Authentification incorrecte

L'authentification incorrecte décrit une situation où, au lieu de valider un ensemble d'informations d'identification, de jetons ou d'autres mécanismes d'authentification, une application fait confiance à un client qui prétend avoir une identité donnée.

Cette situation est très courante lorsqu'il s'agit de connexions à des sites Web qui valident la présence d'un cookie au lieu de valider l'authentification au site. Un cookie peut être falsifié pour prétendre qu'il a déjà été authentifié, et le site web lui fera confiance.

Un autre scénario courant est celui où l'authentification n'est validée que du côté client - permettant ainsi à un attaquant de modifier ou d'interférer avec le logiciel client et d'accéder aux ressources côté serveur en prétendant être déjà authentifié.

13 - Débordement d'entier ou Wraparound

C'est l'une des premières formes de bogues logiciels. Dans une architecture informatique donnée (32/64 bits, ARM, mips, etc.), toute variable occupera une quantité donnée de mémoire. Plus la quantité de mémoire est grande, plus la valeur qui peut être stockée dans cette variable est grande. Cependant, lors d'opérations arithmétiques, il est facile d'omettre de vérifier si le résultat d'une telle opération tient toujours dans la taille mémoire de la variable - comme, par exemple, multiplier deux entiers ensemble et stocker le résultat pourrait conduire à une valeur si grande qu'elle ne tient plus dans la taille maximale de la variable. Dans ce cas, le nombre entier déborde. 

Les différentes architectures d'ordinateur traitent cet événement de différentes manières : certaines ignorent la valeur supplémentaire, ce qui entraîne le stockage d'une valeur erronée ; d'autres écrivent la valeur dans le prochain emplacement mémoire disponible, écrasant ainsi potentiellement quelque chose d'autre déjà stocké à cet endroit ; et d'autres encore enveloppent la valeur - comme le compteur kilométrique d'une voiture qui revient à zéro lorsqu'il atteint la valeur maximale, et vous continuez à conduire.

Si l'on imagine que la variable contrôle le nombre d'itérations dans une boucle, elle peut conduire à une boucle infinie. Dans d'autres scénarios, elle peut provoquer un crash, une application ou un service qui ne répond pas, et elle peut également épuiser la mémoire disponible ou provoquer une instabilité générale du système. Il peut également conduire directement à des scénarios de dépassement de tampon (couverts par un autre bogue de cette liste).

Étant donné que de nombreux langages informatiques n'incluent pas de contrôles de limites par défaut - et doivent être activés explicitement soit par des drapeaux de compilateur, soit par l'utilisation de bibliothèques spécifiques - il est important de prendre une décision éclairée sur le langage à utiliser pour une application en cours de planification. 

Si cela se produit sur une application déjà développée, des contrôles de limites appropriés doivent être ajoutés pour valider que les calculs ne dépasseront jamais les tailles de mémoire variables disponibles, en gardant toujours à l'esprit que, si une application est multiplateforme, les différentes plateformes/architectures auront des tailles autorisées différentes.

Des outils tels que les fuzzers les déclenchent généralement et, à ce titre, ils peuvent être d'une grande utilité pour les développeurs.

Trouver des situations dans le flux d'une application qui sont vulnérables à ce type de bogue est l'une des tâches les plus élémentaires qu'effectue un attaquant. 

12 - Désérialisation de données non fiables

La sérialisation (ou marshaling) est le processus de conversion d'un élément de données stocké en mémoire dans un format qui peut être stocké ou envoyé (via une connexion réseau) à un autre système/application/composant. La désérialisation (ou unmarshaling) est le processus inverse, où un flux de données est converti en une représentation en mémoire d'un objet interne de l'application ("objet" signifiant variable ou groupe de variables).

Lorsqu'une application croit aveuglément que les données qu'elle reçoit - qu'elles soient lues à partir d'un fichier, acceptées à partir d'un formulaire soumis sur un site Web ou par le biais d'une connexion réseau - sont intrinsèquement valides et qu'elle tente ensuite de les désérialiser dans un emplacement mémoire donné, l'application s'expose à des risques. Des flux de données spécialement conçus peuvent inciter une application à écraser des emplacements mémoire existants en dehors de l'emplacement prévu ou à ne pas fournir suffisamment de données pour remplir toutes les variables, ce qui peut conduire à des variables non initialisées ou même à la fourniture de données en dehors des limites prévues, ce qui peut entraîner des événements imprévus.

En règle générale, aucune entrée ne devrait jamais être fiable, sans exception. Même si le développeur écrit lui-même l'application client et l'application serveur, il est toujours possible de détourner une connexion, de tenter d'altérer la communication entre eux ou d'usurper l'identité d'une partie de la conversation, ce qui conduit à un scénario dans lequel les données acceptées ne sont plus dignes de confiance et ne doivent donc pas être désérialisées.

Le code Java suivant semble assez bénin :

try {
File file = new File("object.obj");
ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
javax.swing.JButton button = (javax.swing.JButton) in.readObject();
in.close();
}


[source: https://cwe.mitre.org/data/definitions/502.html]

 

Mais, en regardant de plus près, le fichier "object.obj" n'est pas du tout validé, et pourrait contenir autre chose qu'une définition de bouton, comme l'attend l'application.

11 - Déréférencement du pointeur NULL

Il s'agit de l'une des sources de vulnérabilité les plus courantes dans les applications écrites dans un langage favorable aux pointeurs, comme le C. Elle consiste à essayer d'obtenir la valeur référencée par un pointeur qui a été invalidé précédemment ou qui n'a pas encore été initialisé.

Le résultat habituel, sauf différences architecturales, est que l'application se bloque, car le système d'exploitation détecte la tentative de lecture d'un emplacement mémoire en dehors de l'espace mémoire autorisé de cette application.

L'exemple C# suivant illustre le problème :

 

string name;
int k=0;
if(k==1)
{
  name=Console.ReadLine();
}
Console.Writeline(name.Length);

Lorsque l'exécution atteint le dernier appel, la méthode "Length" sera appelée sur une chaîne de caractères nulle, faisant planter l'application.

Il est intéressant de noter que de nombreux langages ont introduit des changements (types nullables, drapeaux de compilation, nouveaux types d'avertissement) visant spécifiquement à détecter ce type d'erreur au début du processus de développement. D'autres renoncent à l'utilisation explicite des pointeurs pour se prémunir contre ce type d'erreur, mais en dehors de pratiques de programmation prudentes, il n'existe pas de solution miracle efficace à 100 % pour détecter et prévenir les déréférences de pointeurs NULL.

Des situations comme celle décrite dans le bogue n°12, peuvent conduire à l'introduction de valeurs inattendues dans le flux de l'application, déclenchant des déréférences de pointeur NULL.

Une solution relativement simple à ce bogue est de toujours faire précéder toute utilisation de variable appropriée d'une vérification pour valider qu'elle n'est pas NULL, mais la programmation concurrente/parallèle peut rendre cette solution moins optimale.

 

Résumé
Les bogues derrière les vulnérabilités Partie 3
Nom de l'article
Les bogues derrière les vulnérabilités Partie 3
Description
Voici la troisième partie de notre série de blogs en cinq parties qui explore les bogues de code à l'origine des vulnérabilités qui apparaissent chaque jour.
Auteur
Nom de l'éditeur
TuxCare
Logo de l'éditeur

Vous cherchez à automatiser la correction des vulnérabilités sans redémarrage du noyau, temps d'arrêt du système ou fenêtres de maintenance programmées ?

Découvrez le Live Patching avec TuxCare

Devenez rédacteur invité de TuxCare

Commencer

Courrier

Rejoindre

4,500

Professionnels de Linux et de l'Open Source
!

S'abonner à
notre lettre d'information