必须使用参数化查询防止sql注入,like需转义通配符,动态字段名和排序方向须白名单校验,orm原生接口仍需防范拼接风险。

搜索字段不能直接拼进 SQL 字符串
用户输入的搜索关键词一旦用 format()、% 或 + 拼进 SQL,就等于把数据库的控制权交出去。哪怕加了单引号转义、过滤 ' 或 --,攻击者也能绕过——比如用 1' OR '1'='1 或 Unicode 编码绕过前端校验。
真正有效的做法只有一条:所有用户输入必须走参数化查询,不参与 SQL 字符串构造。
-
cursor.execute("SELECT * FROM products WHERE name LIKE %s", (f"%{keyword}%",))✅ -
cursor.execute(f"SELECT * FROM products WHERE name LIKE '%{keyword}%'")❌ -
cursor.execute("SELECT * FROM products WHERE name LIKE '%" + keyword + "%'")❌
LIKE 查询要小心通配符和边界处理
参数化能防注入,但不能自动解决语义问题。比如用户搜 % 或 _,它们在 LIKE 中是通配符,会意外匹配大量数据。
必须显式转义,并在参数中传递转义符:
- PostgreSQL / MySQL:用
转义,SQL 中加ESCAPE '' - SQLite:只认
ESCAPE,不支持反斜杠默认转义 - 正确写法:
cursor.execute("SELECT * FROM items WHERE title LIKE %s ESCAPE '\'", (f"%{keyword.replace('\', '\\').replace('%', '\%').replace('_', '\_')}%",))
动态字段名或排序字段需白名单校验
搜索功能常支持“按价格升序”“按销量降序”,这时 ORDER BY 后的字段名和方向(ASC/DESC)可能来自请求参数。这些不是值,不能塞进参数化占位符里。
必须用硬编码白名单约束:
- 允许的字段:
["title", "price", "created_at"] - 允许的方向:
["ASC", "DESC"] - 校验后拼接:
order_field = request.args.get("sort", "title")→ 先判断order_field in allowed_fields,再拼成f"ORDER BY {order_field} {order_dir}"
ORM 里的 extra()、raw() 和 text() 不是安全出口
Django 的 extra()、SQLAlchemy 的 text() 看起来像 ORM 安全体系的一部分,但它们本质是原生 SQL 接口。只要字符串里有用户输入,就等于开了后门。
User.objects.extra(where=["name LIKE %s"], params=[f"%{q}%"]) ✅User.objects.extra(where=[f"name LIKE '%{q}%'"]) ❌session.execute(text("SELECT * FROM users WHERE name = :q"), {"q": q}) ✅session.execute(text(f"SELECT * FROM users WHERE name = '{q}'")) ❌
最易被忽略的是字段名动态拼接——比如 filter(**{request.GET.get("field"): value}),若 field 是 is_staff__in,就可能触发非预期权限查询。这类地方必须先过白名单。











