sql注入在etl中极易发生,因直接拼接字符串(如where name = '"+user_input+"')会因单引号等特殊字符触发攻击;apache nifi、talend、informatica、dbt、airflow、spark sql等工具若未启用参数化(如preparedstatement、parameters字典、?占位符)或误用jinja模板,均存在高危风险。

直接拼接 SQL 字符串进 ETL 流程,等于给数据管道装了个定时炸弹。只要清洗脚本里出现 WHERE name = '" + user_input + "' 这类写法,哪怕只用一次,就可能在某次上游字段含单引号、分号或注释符时触发注入——尤其当清洗结果要回写数据库或生成报表 SQL 时。
ETL 工具里哪些组件默认不防 SQL 注入
很多 ETL 工具的「SQL 脚本执行」、「动态查询构建」、「自定义 SQL 输入框」等组件,本质就是字符串拼接器。它们不会自动识别变量来源,也不会强制你用参数占位符。
-
Apache NiFi的ExecuteSQL处理器:若使用${sql.query}表达式语言拼接用户字段,且未走ParameterizedSQL模式,即高危 -
Talend的tMysqlRow组件:勾选了「Use PreparedStatement」才启用参数化;默认是普通Statement,拼接字符串直接执行 -
Informatica PowerCenter的SQL Transformation:若在「SQL Query」中写WHERE id = $$INPUT_ID,而 $$INPUT_ID 来自 Mapping 中未校验的端口,就等于开放注入入口 -
dbt的{{ var('filter_value') }}:这是 Jinja 模板渲染,不是参数化——如果filter_value来自外部输入(如 CLI 参数或环境变量),且没做白名单校验,WHERE status = '{{ var("filter_value") }}'就会出事
必须启用的参数化开关和写法
参数化不是“可选项”,是 ETL 数据落地前的最后一道隔离墙。关键不是“能不能用”,而是“有没有强制走预编译路径”。
-
Apache Airflow中用PostgresOperator:必须传parameters字典,而不是把值塞进sql字符串里
✅ 正确:sql="SELECT * FROM users WHERE country = %s", parameters=["CN"]
❌ 错误:sql=f"SELECT * FROM users WHERE country = '{country}'" -
dbt中避免 Jinja 渲染 SQL 值:敏感过滤条件改用WHERE country IN {{ ['CN', 'US'] | inclause }}这类安全宏,或通过ref()/source()引用已清洗表,而非拼接原始输入 -
Talend的tMysqlInput:勾选「Use PreparedStatement」后,SQL 编辑框里必须用?占位,再在「Parameters」表格中逐行填入变量名、类型、值来源字段——不能写WHERE id = ${row1.id} -
Spark SQL(PySpark)中调用spark.sql():禁止直接传 f-string;应改用spark.sql("SELECT * FROM t WHERE dt = ?", [run_date])(需 Spark 3.5+ 支持 JDBC 风格参数化)或先用lit()转为列对象:df.filter(col("dt") == lit(run_date))
清洗阶段最容易被忽略的注入点
很多人以为“只读不写就安全”,但清洗过程中的元数据操作、临时表命名、分区字段构造、甚至日志 SQL 打印,都可能成为突破口。
- 用用户输入拼接临时表名:
CREATE TABLE tmp_user_{user_id} AS ...→ 攻击者传user_id=123; DROP TABLE users;就完蛋 - 日志中打印带参 SQL:
logger.info(f"Running: SELECT * FROM t WHERE code = '{code}'")→ 若日志系统支持 SQL 解析(如 ELK 的 JDBC 插件),恶意字符串可能被二次执行 - 分区字段来自原始 CSV 列:
df.write.partitionBy("region").save(...),而 region 列含us'; DROP TABLE logs; --→ 某些存储格式(如 Hive)在建分区目录时会解析 SQL 片段 - 使用正则替换构造 SQL:
re.sub(r"(\w+)", r"`\1`", user_input)→ 无法防御name`--这类绕过,且不解决语义层面的注入
真正难防的不是明面上的 SQL 拼接,而是那些藏在元数据处理、日志上下文、模板渲染链路里的间接执行路径。只要数据流中任一环节把不可信输入当作代码片段对待,参数化就形同虚设——清洗环节的安全,得从第一行读取数据开始算起。











