参数化查询是防范sql注入的唯一正解,必须用数据库驱动原生占位符(如?、%s)将用户输入严格限定在参数位置,杜绝任何字符串拼接;动态sql元素(表名、字段名等)须白名单校验。

直接拼接SQL字符串会让用户输入变成SQL语法的一部分,数据库不加分辨地执行——这不是“可能被利用”,而是“必然失守”。只要看到 f"WHERE name = '{name}'" 或 "%s" % user_input 这类写法,漏洞就已经存在。
拼接后SQL被当代码执行,不是被当数据处理
数据库驱动(如 sqlite3、psycopg2)只接收一条完整字符串并交给引擎解析。它不会回溯你Python里怎么拼的,更不会帮你过滤单引号或注释符。
- 用户输入
admin' --→ 拼成"SELECT * FROM users WHERE username = 'admin' --' AND password = '...'"→--后全被注释,密码校验消失 - 用户输入
' OR 1=1 LIMIT 10000 --→ 直接拖走最多10000条记录 - 甚至不需要回显:用
' ; DROP TABLE users; --就能删表(取决于驱动是否支持多语句)
所有“看起来安全”的字符串处理都无效
任何在Python层做的替换、截断、转义,都无法替代参数化查询。因为攻击面不在Python,而在SQL解析阶段。
-
name.replace("'", "''")防不住admin' OR 'a'='a这类绕过 -
re.sub(r"[;--]", "", user_input)防不住布尔盲注或时间盲注(不依赖特殊字符) -
str.format()、%、+拼接,哪怕加了类型检查,只要进了SQL字符串,就等于把钥匙交给了攻击者
ORM和text()不是免检通道
用了 Flask-SQLAlchemy 或 db.session.execute(text(...)) 不代表自动免疫。关键看SQL字符串是怎么构造出来的。
-
text(f"SELECT * FROM users WHERE id = {user_id}")——text()只是透传,拼接已污染SQL -
query.filter(**{request.args.get("field"): value})—— 字段名来自用户,可能触发is_staff__in等非预期查询 -
extra(where=["status = '%s'" % status])—— 绕过ORM参数机制,高危
参数化不是“选配”,是唯一正解
必须用数据库驱动原生支持的占位符,并确保所有用户输入只出现在参数位置,绝不出现在SQL字符串中。
-
sqlite3:只认?(位置)或:name(命名),传%s会被当字面量 -
psycopg2:只认%s,第二参数必须是tuple或dict,传列表会报错 - 字段名、表名、
ORDER BY、LIMIT值不能参数化,必须白名单校验或强类型转换(如int(limit)后再拼)
最常被忽略的一点:动态WHERE条件组装时,容易漏掉某个分支的参数化;而只要一个 execute() 调用没走参数化,整个防护体系就形同虚设。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











