直接拼接用户输入进SQL等于交出数据库钥匙;必须用参数化查询(如SQLAlchemy的text()配字典传参)、动态结构部分严格白名单校验、最小权限三者并用才安全。

数据清洗任务里直接拼接用户输入进 SQL,哪怕只在 WHERE 条件里用一次 name = '" + user_input + "',就等于把数据库钥匙交出去了。防不住注入,不是因为没用 ORM,而是因为清洗逻辑绕过了参数化——比如动态字段、Jinja 模板渲染、或手写 text() 查询时传参错位。
SQLAlchemy 的 text() 怎么用才不炸
text() 是安全断点,它不解析、不校验、不转义,只原样转发字符串。安全与否,全看你怎么绑定参数。
- ✅ 正确:占位符必须是
:name形式,传参必须是字典,键名严格匹配:text("SELECT * FROM t WHERE status = :s"), {"s": "active"} - ❌ 错误:
text(f"WHERE name = '{q}'")或text("WHERE name = %s")——%s在text()里只是普通字符,不会触发参数化 - ⚠️ 易踩坑:用元组传参(如
(val,))配:name占位符,会报KeyError;用**locals()传参,键名对不上也崩
filter() 和 filter_by() 看似安全,但清洗脚本里常被误用
这两个方法本身走表达式树,底层生成预编译语句,但前提是“你传进去的是干净值”。清洗任务中容易出事的点在于混用字符串操作。
- ✅ 安全:
query.filter(User.name == name_q)、query.filter_by(status="active") - ❌ 危险:
query.filter("name LIKE '%" + q + "%'")—— 字符串拼接直接跳过 ORM 防护 - ❌ 危险:
query.filter(User.name.like(f"%{q}%"))——f""在 Python 层就拼完了,like()不接管参数化 - ⚠️ 动态字段陷阱:如果排序字段
sort_by来自清洗配置文件,order_by(getattr(User, sort_by))前必须白名单校验:if sort_by not in ["id", "created_at"]
ETL 清洗链路中哪些组件默认不防注入
很多清洗工具的「动态 SQL」入口本质就是字符串拼接器,不会自动识别变量来源。关键不是“能不能写”,而是“有没有强制走预编译路径”。
立即学习“Python免费学习笔记(深入)”;
- Apache NiFi 的
ExecuteSQL:若用${sql.query}表达式拼接字段,且没启用ParameterizedSQL模式,即高危 - Talend 的
tMysqlRow:默认是Statement,必须手动勾选Use PreparedStatement,SQL 编辑框里写WHERE id = ?,再在参数表填字段映射 - dbt 的
{{ var('filter_value') }}:这是 Jinja 渲染,不是参数化。如果filter_value来自 CLI 参数,WHERE status = '{{ var("filter_value") }}'就裸奔 - Spark SQL 的
spark.sql():禁止用f"SELECT * FROM t WHERE x = {user_input}",必须改用spark.sql("SELECT * FROM t WHERE x = ?", user_input)(注意:仅 PySpark 3.5+ 支持该语法)
表名、字段名、ORDER BY 这些根本不能参数化
SQL 协议规定:这些属于查询结构,在解析阶段就必须确定执行计划,数据库根本不允许当参数传。想靠 %s 或 :name 绑定它们,只会报错或降级为字符串拼接。
- 动态表名:用字典硬映射,如
{"user": "dim_user_v2", "order": "fact_order_2024"},绝不用f"SELECT * FROM {table_name}" - 排序字段:白名单校验必须前置,不能等进 SQL 再过滤。前端传
sort=id ASC; DROP TABLE users--,后端不拦住就直接执行 - LIMIT 数量:强制转整型并限范围,
limit_n = max(1, min(100, int(request.args.get("limit", 20)))),避免传负数或超大值拖垮集群
最常被忽略的一点:清洗任务往往要读原始日志、CSV 或 API 返回的脏数据,这些输入连基本类型都没保证。别指望 int() 转换或 RequestParser 就能防注入——它们不介入 SQL 构建环节。真正起作用的,只有参数化绑定 + 结构部分白名单 + 最小数据库权限三者同时生效。


















