Pydantic不能防止SQL注入,仅能做类型和结构校验;真正防护必须依赖参数化查询。它可在数据进入业务逻辑前做字段存在性、范围、格式等轻量兜底,但绝不替代数据库层安全措施。

Pydantic 本身不能修复 SQL 注入,它只做类型校验和结构约束;真正防注入的必须是参数化查询,Pydantic 只能辅助“提前拦住明显非法输入”,但拦不住精心构造的合法字符串。
为什么 Pydantic 的 str 字段校验对 SQL 注入几乎无效
攻击者输入 ' OR '1'='1 完全符合 str 类型、长度也常在允许范围内;Pydantic 不会主动过滤单引号、分号或 SQL 关键字。它只管“是不是字符串”,不管“字符串里藏没藏恶意逻辑”。
- 默认
str字段不做内容清洗,Field(..., min_length=1)拦不住注入载荷 - 用
constr(regex=r'^[a-zA-Z0-9_]+$')做白名单?会直接破坏业务——用户名带中文、邮箱含 @、搜索词含空格都会被拒 - 正则黑名单(如禁止
'、--)容易绕过:/**/UNION/**/SELECT、chr(39)(在某些上下文)仍可能生效
Pydantic 真正该用在哪几个环节
它应在「数据进入业务逻辑前」做轻量、无副作用的结构兜底,而不是假装能替代数据库层防护。
- 强制字段存在性:
user_id: int确保后端拿到的是整数,避免id=123abc被传给WHERE id = %s时因类型隐式转换出问题(虽不直接防注入,但减少意外路径) - 限制数值范围:
page: conint(gt=0, le=1000)防止超大偏移量拖垮查询,间接降低联合注入效率 - 标准化输入格式:
email: EmailStr或url: AnyHttpUrl让后续参数化查询的值更可预期,减少拼接冲动 - 配合
@field_validator做语义级检查(非语法):例如search_keyword长度不超过 200 字符,避免超长 payload 触发缓冲区异常或 WAF 误杀
必须搭配参数化查询才真正起作用
Pydantic 解析后的数据,最终必须进 execute() 的参数字典,绝不能拼进 SQL 字符串。
- 错误写法:
f"SELECT * FROM users WHERE name = '{user.name}'"—— 即使user.name是 Pydantic 模型实例,照样被注入 - 正确写法(SQLAlchemy):
session.execute(text("SELECT * FROM users WHERE name = :name"), {"name": user.name}) - 正确写法(Tortoise ORM):
User.filter(name__icontains=user.name)—— ORM 内部已参数化,但注意别用.raw()回退到字符串拼接 - 特别警惕
IN子句:Pydantic 的List[str]不能直接塞进WHERE id IN (?),需展开为等长占位符:"WHERE id IN ({','.join(['?'] * len(ids))})"+tuple(ids)
最容易被忽略的盲点:Pydantic 模型字段名 ≠ SQL 列名
开发者常把 Pydantic 模型字段名直接当列名用,比如模型有 user_name: str,就写 WHERE user_name = :user_name。但若数据库列实际叫 username,而你又没做映射,就会因字段不存在报错——这时有人会手写字符串拼接“修好”,反而引入新漏洞。
更危险的是排序参数:sort_by: Literal["name", "age"] = "name" 看似安全,但如果拼进 ORDER BY {sort_by}(未加白名单校验+未转义),攻击者仍可传 name, (SELECT SLEEP(5)) 实现时间盲注。真正安全的做法是用字典映射:{"name": "users.name", "age": "users.age"},再取值拼进 SQL —— 这步必须手动控制,Pydantic 不参与。

















