
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インジェクション
影響を受けるバージョン: dagster データベースI/Oマネージャー統合 — 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
すべてのDagsterデータベースI/Oマネージャーは、WHERE句をして構築します。アセットがを使用する場合、パーティションキーはGraphQL API()を通じて実行時に設定できます。これはデフォルトのwebserverデプロイメントでは未認証です。悪意のあるパーティションキーはエスケープされずに(ロード)と(クリーンアップ)の両方のクエリに流れ込み、バックエンドのウェアハウス(Snowflake、BigQuery、DuckDB、DeltaLake、…)に対するSQLインジェクションを引き起こします。
DynamicPartitionsDefinitionaddDynamicPartitionSELECTDELETE同じヘルパー_static_where_clauseが、5つの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のメタデータDBに保存され、addDynamicPartition GraphQLミューテーションを介して実行時に追加されます。デフォルトのdagster-webserverデプロイメントではGraphQLは未認証であるため、ネットワーク上の攻撃者はパーティションキーをエンドツーエンドで供給できます。
チェーン全体の両端を確認しました: GraphQLミューテーションは検証なしで任意のキー文字列を受け入れ、キーはcontext.asset_partition_keysを介して_static_where_clauseにそのまま到達します。
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-string補間は、動的キーに対してはインジェクションになります。修正: f-stringの代わりにパラメータ化クエリ / 適切な識別子・リテラルのクォートを使用する。
poc/poc_partition_sqli.py — 良性キーと悪意のあるキーに対する脆弱な_static_where_clauseの出力を示し、シードされたデータベースに対してライブDuckDBのUNIONベースのデータ持ち出し + DROP TABLEを実行し、攻撃者が送信する正確なaddDynamicPartition / launchRun GraphQLペイロードを表示します。
pip install duckdb # minimal; full chain: dagster dagster-duckdb pandas
python3 poc/poc_partition_sqli.py
Dagsterがオーケストレーションするデータウェアハウスに対する(デフォルト設定での)未認証のSQLインジェクション — 他のテーブルの任意読み取りと破壊的な書き込み。Dagsterはデータプラットフォームの中心に位置するため、スタック内で最も機密性の高いストアに到達します。
GitHub Security Advisoryを通じて責任を持って開示されました。PoCは修正後に公開されました。