SQLAlchemy如何安全为where/order_by传多条件防范SQL注入
SQLAlchemy动态多条件查询防注入改造方案
你原有代码的SQL注入风险来自直接用f-string把用户输入拼接进text()构造的SQL片段,用户传入的恶意参数会被直接编译为SQL语句执行,必须彻底替换这种写法。
核心安全原则
- 所有用户传入的查询参数值,禁止直接拼接进SQL字符串,必须通过SQLAlchemy的原生表达式构造,底层会自动走数据库预编译参数绑定,完全规避注入风险
- 排序字段、排序方向属于SQL语法结构,无法通过参数绑定处理,必须做严格白名单校验,仅允许提前定义的合法值传入
具体改造实现
1. 基础改造(固定过滤、排序规则场景)
如果你当前的过滤、排序字段是固定的(即city对应等值过滤+升序、zip对应大于过滤+降序、serial对应IN过滤+升序,不需要用户自定义排序规则),直接用模型字段构造表达式即可,不需要使用text():
from sqlalchemy import select, and_, asc, desc # 替换为你实际的模型导入路径 from your_models import Archive, Backup filters = [] sorts = [] if 'city' in query: city_val = query['city'] # 模型字段表达式自动做参数绑定,无注入风险 filters.append(Archive.city == city_val) # 注:原代码此处写了等值判断属于笔误,排序逻辑应为指定字段+排序方向 sorts.append(asc(Archive.city)) if 'zip' in query: zip_val = query['zip'] filters.append(Archive.zip > zip_val) sorts.append(desc(Archive.zip)) if 'serial' in query: serial_val = query['serial'] # 处理IN查询参数,兼容用户传入逗号分隔字符串、列表两种格式 if not isinstance(serial_val, (list, tuple)): serial_val = [s.strip() for s in str(serial_val).split(',') if s.strip()] filters.append(Backup.serial.in_(serial_val)) sorts.append(asc(Backup.serial)) with Session(engine) as session: stmt = select(Archive, Backup).join(Backup) if filters: stmt = stmt.where(and_(*filters)) if sorts: stmt = stmt.order_by(*sorts) results = session.exec(stmt).all()
2. 扩展:支持用户动态指定排序规则
如果你需要允许用户通过URL参数自定义排序字段、排序方向,必须增加白名单校验,绝对不能直接使用用户传入的字段名/方向拼接语句:
# 提前定义合法字段、排序方向的映射白名单 ALLOWED_SORT_FIELDS = { "city": Archive.city, "zip": Archive.zip, "serial": Backup.serial } ALLOWED_SORT_DIR = { "asc": asc, "desc": desc } # 读取用户传入的排序参数,走白名单校验 user_sort_field = query.get("sort_by") user_sort_dir = query.get("sort_order", "asc").lower() if user_sort_field in ALLOWED_SORT_FIELDS and user_sort_dir in ALLOWED_SORT_DIR: sort_func = ALLOWED_SORT_DIR[user_sort_dir] sorts.append(sort_func(ALLOWED_SORT_FIELDS[user_sort_field]))
安全说明:上述写法中,所有用户传入的参数值都会被SQLAlchemy作为绑定参数传入数据库驱动,不会和SQL语句字符串做拼接,从根源上阻断SQL注入可能;涉及SQL结构的字段、排序方向全部经过白名单拦截,非法输入会被直接忽略,不存在注入漏洞。
内容的提问来源于stack exchange,提问作者johojojoj
相关产品推荐
相关产品推荐

