{FreeCad} Comment trouver une 0day !

Coucou la compagnie,
Comme à mon habitude j'aime sonder les méandres de l'internet en quête d'actualités qui pourraient m'inspirer malgré elles, des idées de thématique à présenter auprès de mes collaborateurs.
J'ai dernièrement identifié un article explicitant comment un chercheur a découvert via une analyse SAST SCA (Analyse statique d'un code source), une vulnérabilité de type RCE sur un logiciel très réputé de type Slicer (Logiciel permettant d'imprimer des fichiers 3D).
Je me suis donc dit qu'il serait amusant de sonder un peu les logiciels de cet écosystème (l'impression 3D), que j'utilise.
J'ai commencé avec "FreeCad", un petit soft permettant de faire de la FAO (Fabrication Assisté par Ordinateur), c'est un logiciel qui me permet de modéliser des objets en 3 dimensions de façon extrêmement précise.
Première étape : Trouver une fonction dangereuse
FreeCad est développé en python, son code source est public, ainsi il est facile pour n'importe qui d'éplucher ce dernier à la recherche de fonctions sensibles.
Je ne vais pas forcément faire un cours magistral sur les fonctions sensibles, mais je pense qu'avec une petite recherche sur Internet, vous devriez trouver en quelques clics, une liste, avec les mots clés "Python Fonctions Dangereuses".
Pour ma part, j'ai commencé ma recherche avec "eval", une fonction qui permet de prendre une chaine de caractère en paramètre, et de l'exécuter ...
J'ai eu pas mal de résultats !

Deuxième étape : Dérouler la pelote de Laine
Nous avons donc identifié un certain nombre d'endroits avec des fonctions "sensibles", désormais
il va nous falloir regarder ce que ces fonctions sensibles prennent en "paramètre", et éliminer de notre "liste" tous les endroits contenant une fonction sensible dont le / les paramètres ne sont pas "dynamique(s)" (là où il n'est pas possible d'agir).
Pour cela, on prend chaque fichier et on part de la ligne potentiellement "vulnérable", puis on regarde ce quelle prend en paramètre :
- S'agit-il d'une donnée déjà définie ?
- Si ce n'est pas le cas, existe t-il des contrôles avant ? (Contrôle sur le type : Entier, Float, Array, ... ?)
- Le paramètre est-il accessible à l'extérieur du logiciel ?
J'ai donc identifié dans le fichier "./Mod/Path/Path/Preferences.py", une utilisation de la fonction "eval"

Si je devais traduire en Français cet enchainement :
Lorsque la fonction "postProcessorBlacklist" est appelée, un bloc de configuration est chargé : /BaseApp/Preferences/Mod/Path
Ce bloc est issue d'un fichier XML ressemblant à ceci :
<FCParameters>
<FCParamGroup Name="Root">
<FCParamGroup Name="BaseApp">
<FCParamGroup Name="Preferences">
<FCParamGroup Name="Mod">
<FCParamGroup Name="Path">
..... Ensemble de paramètres ...
</FCParamGroup>
</FCParamGroup>
</FCParamGroup>
</FCParamGroup>
</FCParamGroup>
</FCParameters>
De ce bloc contenant un ensemble de paramètres, est extraite la valeur du paramètre "postProcessorBlackList" (si celui-ci existe).
Si celui-ci n'existe pas, un tableau vide est renvoyés.
Sinon, cette valeur est "évaluée" (ce qu'elle contient est exécuté en comme du code)...
Troisième étape : Comprendre la vulnérabilité
Le développeur, par l'utilisation d'un "eval", souhaite convertir une chaine de caractère en un tableau.
(1) La fonction eval va interpréter la valeur récupérée, que celle-ci contienne du code "fonctionnel" ou une valeur "statique". elle va donc être capable de faire ce que le développeur attend, mais permet davantage de chose que de simplement faire une conversion de tableau.
Elle permet aussi d'exécuter directement du code arbitraire (fais ceci, fais cela, ....)
C'est donc une fonction "risquée" dans un contexte où on ne connait pas d'avance la valeur "exécutée".
(2) La valeur de "postProcessorBlackList" n'est pas contrôlé : il aurait été prudent de la part du développeur de s'assurer via une expression régulière ou une fonction native, que sa valeur ressemble ou est bien un tableau (une "liste") : [ 'value1', 'value2', ....]
(3) Il existe des fonctions qui permettent de faire très précisément l'opération de conversion et de façon sécurisé. L'utilisation du composant JSON est une façon sécurisée de convertir une chaine en un autre type de données sans exécuter de code arbitraire. Le composant numpy est également un bon contrôle pour garantir que la donnée "convertie" est bien du type attendue (ici une liste).
import json
import numpy as np
....
def postProcessorBlacklist():
pref = preferences()
blacklist = json.loads(pref.GetString(PostProcessorBlacklist, ""))
if not blacklist or type(blacklist) is not list :
return []
return blacklist #eval(blacklist)
Ci-dessus le code "corrigé", j'y ai ajouté l'utilisation du composant JSON pour convertir la valeur récupérée dans le type qui convient (integer, float, array, string, list, ...).
L'ajout d'un contrôle pour vérifier que la conversion renvoie bien une "liste".
Je termine par la suppression du "eval" car la donnée renvoyée est désormais une liste.
Quatrième étape : Tester la vulnérabilité
Nous savons à présent que le code est vulnérable en un endroit (au moins, cet article ne fait état que de ce seul exemple).
Nous savons que celui-ci va extraire une ligne d'un fichier de configuration et au lieu de traiter cette donnée comme un paramètre, celui-ci va l'exécuter comme du code.
Reste donc à comprendre où est le fichier de configuration, puis de tester si en injectant du code dans le paramètre en question, celui-ci est bien exécuté.
NB : Je ne ferais pas du cas par cas, pour chaque système d'exploitation pour cet article, j'utiliserais Windows, je vais donc continuer cette présentation sur ce principe : aussi bien dans les "chemins de fichier" que dans la commande que j'exécuterais.
J'accède donc sur Windows, au répertoire qui contient les "configurations" des applications via un CTRL + R et l'instruction "%APPDATA%"

Ensuite je me rends dans le répertoire de Freecad

De là nous disposons de 2 fichiers de configuration "system.cfg" et "user.cfg".
Celui qui nous intéresse est "user.cfg" : je vais l'éditer avec un éditeur de texte.

Comme indiqué plus haut, la configuration "vulnérable" se situe dans l'arborescence suivante :
/BaseApp /Preferences /Mod /Path
Pour vous faire gagner du temps, il vous suffit ici de chercher "Path" avec les guillemets (il ne devrait y avoir qu'une occurence)

Nous trouvons ici un bloc de configuration, mais celui-ci est vide.
Ajoutons donc le paramètre postProcessorBlacklist avec notre valeur "malveillante" (un bout de code qui exécute une commande Windows pour ouvrir la calculatrice, et qui renvoi le résultat de cette instruction sous forme d'une chaine de caractère.)

J'ai testé d'ouvrir "FreeCad", de me rendre sur le module vulnérable, d'accéder aux préférences .... échec .... rien ne se passe ....
J'opte donc pour une autre approche :
je vais tout d'abord tenter de "générer" la configuration "fonctionnelle" (avec des "bonnes" données).
Une fois cela fait, je vais tenter de l'altérer.
Je me rends donc sur le module "Path" (celui-ci qui contient le code vulnérable).

Par la suite j'accède aux "préférences" (menu "édition" > "préférences").
Une fois dans les préférences, je vais me promener un peu partout, en quête du menu lié aux réglages "post-processeur".

C'est donc ici que tout ce joue.
A première vue, je ne vois rien qui ne soit lié à une quelconque BlackList.
J'en conclu que cette "blacklist" est issue de la liste des processeurs existants qui auraient été décochés dans l'interface.

Je vais donc en décocher quelques uns, puis "enregistrer / appliquer", et voir ce qu'ils se passe dans le fichier de configuration.

Ici on constate bien que les "post-processeurs" décochés sont directement ajoutés dans la blacklist du fichier de configuration, ainsi qu'un ensemble de paramètres sont également ajoutés.
Il est très possible que notre précédent test est échoué par l'absence de l'un ou l'autre de ces paramètres.
Je vais alors refaire l'opération en remplaçant cette "blacklist" par mon instruction malveillante !

Surprise !
En me rendant sur le module "Path", et en accédant aux "préférences", la calculatrice s'ouvre !

Cinquième étape : Prévenir / Partager & Sensibiliser
Il ne s'agit pas là d'ajouter un trophée sur un mur pour clamer à quel point la cybersécurité est un mal nécessaire ! Et qu'il faut glorifier le fait de cracker et pirater tout ou n'importe quoi.
Mais je pense que d'aborder de façon pédagogique la "démarche", le "mode opératoire" pour détecter, comprendre et neutraliser les vulnérabilités d'une façon "pratique" et non théorique est certainement plus "excitant" pour des novices dans ce domaine que de faire un court magistral !
Il s'agit là de sensibiliser toute personne qui touche de près ou de loin à la technologie.
Vous voici donc prévenu !
Il ne fait aucun doute que cette vulnérabilité sera patché rapidement après la publication de cet article !
Conclusion
J'espère que cette article vous aura plus, bien évidemment "FreeCad" sera très certainement patché et si vous lisez cet article plusieurs années après sa parution celui-ci ne sera plus d'actualité (en dehors du mode opératoire). N'hésitez pas à commenter, partager ou faire part de vos expériences par commentaire !
La criticité de cette vulnérabilité n'est pas très élevée, pour l'exploiter il faut altérer un fichier de configuration : il faut soit un accès physique, soit une application malveillante déjà installée sur la machine.
Très bonne journée à vous !