
Внедрение кода (RCE) в datamodel-code-generator через непроверенный customBasePath (CVE-2026-63720)
Серьёзность: Высокая, CVSS 3.1 7.5 / CVSS 4.0 7.5 (присвоен VulnCheck, CNA)
Экологический потолок (развёртывание в виде сетевого сервиса): до 9.8
Вектор (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
Вектор (v3.1): CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H
Затронуто: datamodel-code-generator < 0.70.0
Исправлено в: 0.70.0
CWE: CWE-94 (Некорректный контроль генерации кода, «Инъекция кода»)
Сообщил: Rahul Karne
CNA: VulnCheck
Опубликовано: 26 июля 2026 года
datamodel-code-generator проверял каждую управляемую схемой строку импорта, которая могла нести полезную нагрузку для инъекции кода, кроме одной.
Инструмент преобразует входную схему (JSON Schema, OpenAPI, YAML) в исходный код моделей Python. Некоторые поля схемы попадают непосредственно в генерируемый код, поэтому проект проверяет их как точечные Python-идентификаторы перед использованием — специально для предотвращения инъекций. Поле расширения схемы customBasePath — единственное родственное поле, которое пропускает эту проверку. Его значение попадает без санитизации в оператор from ... import ... в генерируемом выводе. Атакующий, контролирующий входную схему, может внедрить произвольный код Python с помощью переводов строк и выражения без точек; он выполняется в момент импорта сгенерированного модуля, что является обычным следующим шагом после генерации моделей.
Это неполное исправление CVE-2026-55415 (GHSA-5578-w22f-pfx9), которое ужесточило защиту родственных полей customTypePath и x-python-import от именно этого класса атак. То исправление не затронуло customBasePath, который достигает того же стока без проверки и оставался эксплуатируемым вплоть до 0.68.1 и в main до 0.70.0.
Произвольное выполнение кода Python в процессе, который импортирует или запускает сгенерированные модели: на машине разработчика, в CI-раннере или в любом сервисе, который генерирует, а затем загружает модели. Конфиденциальность, целостность и доступность хоста полностью скомпрометированы и ограничены только привилегиями этого процесса.
Серьёзность полностью зависит от того, где выполняется кодогенерация по недоверенным входным данным:
Кто затронут: Любое использование datamodel-code-generator < 0.70.0, которое (1) генерирует модели из схемы, значение customBasePath в которой подвержено влиянию атакующего, и (2) импортирует или выполняет сгенерированный модуль. Стандартный workflow «сначала кодогенерация, затем импорт» по своей сути удовлетворяет условию (2).
Кто не затронут:
0.70.0 или новее, где customBasePath проверяется.| Метрика | Значение | Источник |
|---|---|---|
| Загрузок за всё время | 194 миллиона | pepy.tech/projects/datamodel-code-generator |
| Загрузок за последние 30 дней | 16,3 миллиона | pepy.tech |
| Типичное развёртывание | Машины разработчиков, CI/CD-конвейеры и платформы генерации SDK, выполняющие кодогенерацию из OpenAPI / JSON Schema | присуще функции инструмента |
Значение поля схемы customBasePath попадает в генерируемый код без каких-либо ограничений на идентификатор. Важны три точки в кодовой базе (пути указаны относительно src/datamodel_code_generator/):
parser/jsonschema.py определяется поле custom_base_path с alias="customBasePath" (строка ~644), которое используется через _resolve_base_class(...) в нескольких местах вызова.parser/base.py, _resolve_base_class (строка ~1665), возвращает значение только после локального normalize() (дедупликация/обрезка). Проверка идентификатора не применяется.imports.py, Import.from_full_path() (строка ~35), выводит значение дословно в виде строки from ... import .... Значение также используется как базовый класс в model/base.py set_base_class (строка ~1324) и подставляется как есть шаблоном модели (class {{ class_name }}({{ base_class }}):).Поскольку значение записывается в исходный код Python без ограничений, встроенные переводы строк и выражение без точек сохраняются в выводе как отдельные разбираемые строки, и средняя строка выполняется при импорте.
Полезная нагрузка по необходимости не содержит точек. Import.from_full_path разбивает значение по символу ., поэтому обычный вызов os.system(...) был бы разорван. Использование getattr(__import__('os'),'system')(...) позволяет избежать любых ., сохраняя разрешение того же вызова, а окружающие переводы строк сохраняют синтаксическую корректность выводимых строк from ... import ..., благодаря чему внедрённая средняя строка выполняется без ошибок.
Это не проект, который пренебрегал защитой от инъекций. Мейнтейнер неоднократно ужесточал защиту именно от этого класса атак в нескольких бюллетенях (GHSA-5578, m34r, 8m8r, wjv6), каждый раз пропуская управляемую схемой строку импорта или типа через _validate_dotted_python_identifier_path до того, как она достигнет кодогенерации. Родственные поля customTypePath (проверяется в parser/jsonschema.py, строки ~4956, 5202) и x-python-import (строка ~2096) обе проходят через этот валидатор.
customBasePath — единственное родственное поле без такого вызова. Он достигает того же стока Import.from_full_path другим путём (_resolve_base_class), который никогда не был подключён к проверке, применявшейся к другим полям. Дефект сохранился именно потому, что окружающая защита выглядела полной: рецензент, ищущий непроверяемые строки импорта, видит валидаторы на тех полях, которые проверяет в первую очередь, а этот параметр проходит через хелпер, который выглядит как разрешение базового класса, а не как обработка импорта. Это пробел в систематическом исправлении, а не его отсутствие — именно поэтому он сохранился вплоть до последнего релиза.
Атакующему необходимо:
< 0.70.0.customBasePath в схеме, которую будет обрабатывать цель, — на практике это достигается предоставлением или изменением входной схемы (сторонний документ OpenAPI/JSON Schema или схема, отправленная в сервис).