99%的Python SQL注入漏洞源于用户输入拼接SQL字符串;修复核心是所有动态值必须走驱动原生参数化接口且传参类型严格匹配,表名、字段名等结构化内容须白名单校验。

直接上结论:*99% 的 Python SQL 注入漏洞,根源在于把用户输入拼进 SQL 字符串——只要用了 f"SELECT FROM user WHERE id = {user_id}"、"%s" % user_id 或 .format(),就已失守。修复核心只有一条:所有动态值必须走驱动原生支持的参数化接口,且传参类型严格匹配。**
psycopg2 / pymysql / sqlite3 占位符语法不能混用
不同数据库驱动对占位符的解析规则完全不同,硬套一种写法在换库时必然出错或失效:
-
sqlite3只认?(位置)或:name(命名),传%s会被当字面量处理,不报错但查不到数据 -
psycopg2只认%s,第二参数必须是tuple或dict;传[user_id]会抛TypeError: not all arguments converted -
pymysql默认支持%s,但不支持%()s命名格式;若要用命名参数,得显式开启named=True
正确示例(psycopg2):cursor.execute("SELECT * FROM user WHERE id = %s AND status = %s", (user_id, "active"))
错误示例:cursor.execute("SELECT * FROM user WHERE id = %s", [user_id]) —— 列表不被接受cursor.execute(f"SELECT * FROM user WHERE name = '{name}'") —— 字符串拼接,高危
Django ORM 的三个“安全假象”场景
Django 查询集本身安全,但以下三处常被误认为“用了 ORM 就万事大吉”:
-
extra()和raw():它们绕过 ORM 层,直接执行 SQL;参数必须走params=关键字传入,不能拼进字符串里 -
filter(**{field_name: value}):若field_name来自用户请求(如搜索字段),攻击者可传__dict__或is_staff等字段名,触发非预期查询甚至数据泄露 -
Q对象构造时用了eval()或format():比如Q(eval(request.GET.get("q_expr"))),等于把控制权交给了攻击者
安全做法是白名单校验:
allowed_fields = {"id","name","email","created_at"}<br>
field = request.GET.get("sort_by","id")<br>
if field not in allowed_fields:<br>
field = "id"<br>
queryset = User.objects.order_by(field)
SQLAlchemy 的 text() 不是免检通行证
text() 常被当作“ORM 里写原生 SQL 的安全出口”,但它只在满足两个条件时才真正防注入:
立即学习“Python免费学习笔记(深入)”;
- SQL 字符串必须是纯静态的,不含任何用户输入
- 所有动态值必须通过第二参数(
dict或tuple)传入,且占位符只能是:name(命名)或?(位置)
错误写法:text(f"SELECT * FROM user WHERE id = {user_id}") —— 拼接即破防text("SELECT * FROM user WHERE id = :id").bindparams(id=user_id) —— 安全
最易被忽略的一点:表名、字段名、ORDER BY 子句等结构化内容无法用参数化方式传入,必须靠白名单硬校验。哪怕你所有值都用了 %s,如果 ORDER BY 后面跟的是用户可控的字符串,照样能注入。


















