filter() 和 filter_by() 安全,但仅限字段名硬编码且值全用绑定参数;text() 需严格使用 :name 占位符+字典传参,并对动态结构白名单校验。

filter() 和 filter_by() 真的安全吗?
安全,但仅限于字段名硬编码、值全走绑定参数的场景。它们底层构造表达式树,最终生成带 %s 或 :name 占位符的预编译语句,数据库驱动把用户输入当纯数据传入——不解析、不重编译。
这些写法是安全的:
-
filter_by(username=request.args.get("u")):字段名username是字面量,只支持等值匹配 -
filter(User.status == request.args.get("status"), User.created_at > now):所有右侧值(含None、datetime、字符串)都进参数绑定
这些写法立刻失效:
-
filter(User.name.like(f"%{q}%")):f-string 在 Python 层已拼出完整 SQL 片段,like()不接管参数化 -
filter(text("name = '" + q + "'")):绕过 ORM,直接交字符串给数据库
text() 是安全边界断裂点,怎么用才不翻车?
text() 本身不解析 SQL,只原样转发。安全与否,全看你怎么传参。
错误写法(全部高危):
text(f"SELECT * FROM users WHERE id = {user_id}")text("WHERE name = '" + name + "'")-
text("ORDER BY " + sort_field):哪怕sort_field是"id ASC; DROP TABLE users--",也会执行
正确写法(必须同时满足):
- 占位符用
:name形式,?name或%s在text()中无效 - 第二个参数必须是字典,如
{"name": name, "status": "active"};不能用元组(易错位),也不能用**locals()(键名可能不匹配) - 动态结构部分(如
ORDER BY字段)必须白名单校验:if sort_field not in ["id", "created_at", "username"]: raise ValueError("Invalid sort field")
db.session.execute() 的两种调用方式风险天差地别
新手常误以为用了 Flask-SQLAlchemy 就自动免疫,结果在裸字符串拼接里翻车。
创建 Flask + Vue.js 全栈项目,采用单端口架构、uv 依赖管理、结构化日志和生产级前端设计。当用户要求以下操作时触发:创建新的 Flask+Vue 项目、搭建 Web 应用、构建全栈 Python+Vue 应用、初始化 Flask Web 项目、设置单端口 Flask 前后端项目。触发词:新建项目、创建项目、搭建项目、Flask项目、Vue项目。
危险写法(完全绕过参数机制):
db.session.execute("UPDATE users SET status = 'active' WHERE id = {}".format(user_id))db.session.execute(f"DELETE FROM logs WHERE ts
安全写法只有两类:
- ORM 对象操作:
user.status = "active"; db.session.commit() - 带参数的
text():db.session.execute(text("UPDATE users SET status = :status WHERE id = :id"), {"status": "active", "id": user_id})
注意:db.session.connection().execute() 同样要遵守参数化规则,比如 conn.execute("SELECT * FROM users WHERE id = %s", [1])(PostgreSQL)或 conn.execute("SELECT * FROM users WHERE id = ?", [1])(SQLite)。
哪些东西根本不能参数化?白名单校验为什么不可省略
表名、列名、ORDER BY 字段、GROUP BY、LIMIT 偏移量——这些属于 SQL 查询结构,在数据库解析阶段就决定执行计划,协议层面禁止作为参数传入。这不是 SQLAlchemy 的限制,是 PostgreSQL/MySQL/SQLite 的共性硬约束。
所以这些必须白名单校验:
-
getattr(User, field)前检查field in User.__table__.columns.keys() -
order_by(request.args.get("sort"))前过滤字段和方向:if sort not in ["id", "created_at"] or direction not in ["ASC", "DESC"] - 动态表名(如分表场景)必须从预定义列表中取:
table_name = {"log_2026": Log2026, "log_2025": Log2025}.get(year_suffix)
别信 replace("'", "''") 或正则删 --,十六进制编码、Unicode 变体、盲注都可绕过。白名单是唯一可靠手段。










