
Detailed CVE-2026-41490 disclosure with PoC demonstrating unauthenticated SQL injection in Dagster database I/O managers via dynamic partition keys, affecting DuckDB, Snowflake, BigQuery, and DeltaLake integrations.
Серьёзность: Высокая (CVSS 8.x — AV:N/AC:L/PR:L/UI:N + C:H/I:H/A:H)
CWE: CWE-89 — SQL-инъекция
Затронутые версии: интеграции I/O-менеджеров баз данных dagster — dagster-duckdb, dagster-snowflake, dagster-gcp (BigQuery), dagster-deltalake, dagster-snowflake-polars (≤ 1.12.20)
Консультация: GHSA-mjw2-v2hm-wj34
NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-41490
Автор: Romain Deperne
Каждый I/O-менеджер базы данных Dagster строит своё выражение WHERE, интерполируя значения ключей разделов напрямую в SQL через f-строки. Когда актив использует DynamicPartitionsDefinition, ключи разделов могут быть установлены во время выполнения через GraphQL API (addDynamicPartition) — который в стандартной развёртке веб-сервера не требует аутентификации. Вредоносный ключ раздела попадает без экранирования как в запросы SELECT (загрузка), так и в DELETE (очистка), что приводит к SQL-инъекции в поддерживаемое хранилище (Snowflake, BigQuery, DuckDB, DeltaLake, …).
Одна и та же вспомогательная функция _static_where_clause скопирована в пять пакетов I/O-менеджеров. В каждом из них она делает:
def _static_where_clause(table_partition):
partitions = ", ".join(f"'{partition}'" for partition in table_partition.partitions)
return f"""{table_partition.partition_expr} in ({partitions})"""
partition обёрнут в одинарные кавычки без экранирования. Вопрос заключался в том, может ли partition когда-либо контролироваться атакующим. Для статических разделов он определяется разработчиком — неинтересно. Для DynamicPartitionsDefinition ключи хранятся в мета-БД Dagster и добавляются во время выполнения через GraphQL-мутацию addDynamicPartition. В стандартном развёртывании dagster-webserver GraphQL не требует аутентификации, поэтому атакующий в сети может полностью контролировать ключ раздела.
Я подтвердил оба звена цепи: GraphQL-мутация принимает произвольные строки ключей без проверки, и ключ достигает _static_where_clause без изменений через context.asset_partition_keys.
addDynamicPartition(... partitionKey: "') UNION SELECT username, password_hash FROM secret_table; --")launchRun(...), нацеленный на этот разделSELECT/DELETE ... WHERE col in ('') UNION SELECT ... ; --')| Пакет | Файл |
|---|---|
| dagster-duckdb | io_manager.py:340-342 |
| dagster-snowflake | snowflake_io_manager.py:434-436 |
| dagster-gcp (BigQuery) | bigquery/io_manager.py:472-474 |
| dagster-deltalake | io_manager.py:265-267 |
| dagster-snowflake-polars | snowflake_polars_type_handler.py:74 |
Как путь загрузки, так и путь очистки используют его:
query = f"SELECT {col_str} FROM {schema}.{table} WHERE\n" + _partition_where_clause(...) # read
query = f"DELETE FROM {schema}.{table} WHERE\n" + _partition_where_clause(...) # write
Ключи разделов считались доверенными константами разработчика, но DynamicPartitionsDefinition превращает их в данные, устанавливаемые извне во время выполнения. Интерполяция f-строк, которая была «безопасной» для статических ключей, становится инъекцией для динамических. Исправление: параметризованные запросы / правильное цитирование идентификаторов и литералов вместо f-строк.
poc/poc_partition_sqli.py — показывает вывод уязвимой функции _static_where_clause для безобидных и вредоносных ключей, выполняет живую DuckDB UNION-основанную эксфильтрацию + DROP TABLE против заполненной базы данных и выводит точные полезные нагрузки GraphQL addDynamicPartition / launchRun, которые отправил бы атакующий.
pip install duckdb # минимально; полная цепь: dagster dagster-duckdb pandas
python3 poc/poc_partition_sqli.py
Неаутентифицированная (стандартная конфигурация) SQL-инъекция против хранилища данных, которым управляет Dagster — произвольное чтение других таблиц и деструктивные записи. Dagster находится в центре платформ данных, поэтому уязвимость затрагивает наиболее чувствительное хранилище в стеке.
Раскрыто ответственно через GitHub Security Advisory. PoC опубликовано после исправления.