动态表名不能参数化,必须白名单校验;sql语法层禁止对表名、字段名等标识符参数化,%s或:name仅支持值替换,不支持users_v2等标识符;应通过预定义字典映射(如table_map)将用户输入作为键查出可信表名,再拼入sql。

动态表名不能参数化,必须白名单校验
SQL语法层根本不允许对表名、字段名这类标识符做参数化——%s或:name只能替换成值,不能替换成users_v2或order_history_2024。所有试图用占位符“参数化表名”的写法,本质都是字符串拼接,等于主动绕过防护。
用字典映射代替字符串拼接
最稳妥的做法是把用户输入当作键,查预定义的合法表名映射表。这样既支持动态路由,又完全隔离不可信输入。
- 定义固定映射:
TABLE_MAP = {"user": "tbl_user_active", "archive": "tbl_user_archive_2024", "temp": "tmp_import_buffer"} - 校验并取值:
table_key = request.args.get("table")→if table_key not in TABLE_MAP: raise ValueError("Invalid table key")→real_table = TABLE_MAP[table_key] - 拼进SQL时只用
real_table,它来自字典而非用户输入
正则过滤仅作辅助,不能替代白名单
正则如^[a-zA-Z_][a-zA-Z0-9_]{1,63}$能拦掉明显非法字符,但无法防止语义级攻击(比如表名存在但权限不足、或指向日志表/系统表)。更危险的是,有人会用__getattr__或__subclasses__触发元编程逻辑,正则完全无感。
- 正则适合做前置长度和字符集限制(如
max_len=64、禁止.;--/*) - 但只要涉及业务逻辑路由,就必须走白名单;正则只是多一层沙箱,不是防线主体
Django和SQLAlchemy里怎么落地
在ORM中直接拼表名会立刻失守。Django没有公开接口让你改Meta.db_table运行时值;SQLAlchemy的Table(..., autoload_with=engine)也不接受动态字符串。
- Django:别碰
extra(tables=["..."])或raw("FROM ...")里的表名,改用多个固定Model分别查,再合并结果 - SQLAlchemy:用
Table(name, metadata, autoload_with=engine)时,name必须来自TABLE_MAP,绝不能是request.args["table"] - 如果真要跨表联合,优先考虑数据库视图(view),把动态逻辑收口到DB层,应用只查固定视图名
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











