filter_by() 更安全,因其仅支持字段名等值匹配、无法传入表达式;filter() 功能强但需确保所有参数来自可信源或已清洗,误用仍可引入风险。

SQLAlchemy 的 filter() 和 filter_by() 哪个更安全?
两者本身都默认参数化,不会直接拼接 SQL,但用法不当照样引入风险。关键不在函数名,而在你传进去的是什么。
常见错误现象:filter(User.name == request.args.get('name')) 看似正常,但如果前端传入 "admin' -- ",而你没做任何校验或类型约束,ORM 仍会原样当字符串值处理——这本身不危险;但若后续有人把 request.args.get('name') 错误地塞进 text() 或 execute(),就彻底绕过防护了。
-
filter_by()只支持等值匹配,键必须是模型字段名,不能传表达式,相对更“傻瓜”,适合简单场景 -
filter()支持复杂表达式(如User.age > 18),但要求所有值都来自可信来源或已清洗;别把原始请求参数直接丢进like()或in_()而不做长度/字符集限制 - 如果字段名本身来自用户输入(比如动态排序字段),必须白名单校验,绝不能直接拼进
getattr(User, field_name)
Django ORM 的 extra() 和 raw() 是不是高危操作?
是。这两个方法明确绕过查询构建器,直接接触 SQL 字符串,哪怕只拼一个变量,也等于打开注入大门。
使用场景:仅限极少数无法用 QuerySet 表达的复杂聚合、数据库特有函数调用,且必须确保所有外部输入都经过严格过滤。
立即学习“Python免费学习笔记(深入)”;
-
extra()中的where或params参数,如果params是字典,值会被参数化;但tables、where字符串里的字段名/表名绝不允许来自用户输入 -
raw()的 SQL 字符串里,任何用户输入都必须先通过connection.cursor().mogrify()模拟绑定,或改用cursor.execute(sql, params)形式——但更推荐直接放弃raw(),用annotate()+F()+ 数据库函数替代 - 一个典型坑:
Book.objects.raw("SELECT * FROM book WHERE title LIKE '%" + user_input + "%'")—— 这行代码不管用什么 ORM,都等于裸奔
手写原生 SQL 时,psycopg2 和 sqlite3 的参数占位符写法有什么区别?
不只是写法不同,不统一用法会导致参数未被绑定,变成字符串拼接。
性能影响:占位符解析由驱动层完成,和硬拼字符串相比几乎没有开销;但错用会引发 SQL 解析失败或隐式类型转换问题。
-
psycopg2必须用%s(注意是字面量%s,不是 Python 的%格式化),参数传 tuple 或 dict:cursor.execute("SELECT * FROM user WHERE id = %s", (user_id,)) -
sqlite3支持?、:name、:1三种,但不支持%s;用%s会直接报错或静默拼接:cursor.execute("SELECT * FROM user WHERE name = ?", (name,)) - 千万别混用:
"WHERE id = %s".format(id)或f"WHERE id = {id}"—— 这些在任何驱动下都是高危操作
为什么对用户输入做 strip() 或正则替换不能代替参数化?
因为过滤只是辅助手段,不是安全边界。SQL 注入的本质是语义混淆,不是字符黑名单能覆盖的。
容易踩的坑:看到别人删掉了单引号、分号就以为万事大吉,结果遇到宽字节、编码绕过、注释符嵌套、或数据库特有的 escape 序列(如 MySQL 的 \')依然中招。
- 正则替换可能漏掉 Unicode 等价字符(如全角单引号、零宽空格),或误杀合法内容(比如用户名含撇号 “O’Neill”)
-
strip()对" admin' OR '1'='1"这类 payload 完全无效 - 唯一可靠的防线只有参数化:让数据和 SQL 结构彻底分离,数据库引擎自己负责转义和类型绑定
参数化不是“加个括号就完事”的动作,而是贯穿查询构建全程的约束意识——从最外层的 ORM 方法选择,到内层 raw SQL 的占位符写法,再到动态字段名的白名单控制,每一步偏离都会让防护失效。










