参数化查询是防范SQL注入最有效手段,必须对所有不可信数据源(如Excel、API、爬虫)使用占位符(?、%s或:name),严禁字符串拼接;不同数据库驱动占位符不可混用,动态表名/字段名须经白名单校验。

只要入库逻辑里有 cursor.execute() 拼接用户输入,就存在被注入风险——哪怕只处理 Excel 导入、API 批量写入或定时爬虫落库,只要数据源不可信,拼字符串就是开后门。
用对占位符:不同驱动的 %s、?、:name 不能混用
常见错误是把 SQLite 的 ? 写成 MySQL 的 %s,或反过来。报错不是语法错,而是 TypeError: not all arguments converted 或静默执行失败。
- sqlite3 只认
?(问号),参数必须是元组,哪怕单个值也要写成(value,) - pymysql / mysql-connector-python 用
%s,不加引号,参数也是元组 - psycopg2(PostgreSQL)默认用
%s,但支持%(key)s命名式,需传字典{"key": value} - SQLAlchemy 的
text()如果手动拼接,仍要显式用:param并绑定,否则无效
批量入库时别用 execute() 循环,改用 executemany()
循环调用 cursor.execute() 不仅慢,还容易在中间出错导致部分写入;更危险的是有人图省事写成 f"INSERT INTO t VALUES ('{row[0]}', '{row[1]}')" ——这是把注入漏洞批量复制 N 次。
- 正确做法:用
cursor.executemany("INSERT INTO t VALUES (%s, %s)", data_list) -
data_list是元组列表,如[("a", 1), ("b", 2)],驱动自动为每组参数做绑定 - 如果字段动态变化(比如导入表头不定),先用白名单校验列名,再生成固定占位符语句,不要用
format()拼表名或字段名
ORM 批量写入也得防 text() 和 extra() 这类“后门”
SQLAlchemy 的 session.bulk_insert_mappings() 或 Django 的 bulk_create() 是安全的,但一旦出现 text("SELECT * FROM " + user_table) 或 extra(where=["name = '" + name + "'"]),防护就归零。
立即学习“Python免费学习笔记(深入)”;
- 动态表名/字段名必须走白名单:定义允许的表名集合,用
if table_name not in ALLOWED_TABLES:拦住 - Django 的
extra()、raw(),SQLAlchemy 的text()、select().where(text()),全部视为高危操作,禁止接收任何用户输入 - 如果真要动态排序,用
order_by(getattr(Model, field))配合字段白名单,而不是拼 SQL 字符串
最易被忽略的一点:自动化入库往往绕过 Web 层的输入校验,直接读文件或调内部 API,所以防御必须落在 DAO 层本身——占位符写对只是起点,动态结构控制和权限隔离才是兜底防线。


















