Python Web 接口防止 SQL 注入攻击怎么使用参数化查询

文章导读
直接在数据库驱动层使用参数化查询是防止 SQL 注入最有效的方法,适用于所有基于 SQL 数据库的 Python Web 项目。
📋 目录
  1. 完整代码示例
  2. 验证方法
  3. 常见注意事项
  4. 参考来源
A A

直接在数据库驱动层使用参数化查询是防止 SQL 注入最有效的方法,适用于所有基于 SQL 数据库的 Python Web 项目。

核心建议:严禁拼接 SQL 字符串,必须使用数据库驱动提供的占位符机制传递参数,这是消除注入风险的根本手段。

  • 检查:搜索代码中是否存在 f-string、format 或 + 号拼接 SQL 语句的情况。
  • 修改:将拼接变量改为驱动支持的占位符(如 %s 或 ?),并将数据作为独立参数传入。
  • 验证:使用包含单引号、分号等特殊字符的输入测试接口,确认不会报错或执行意外逻辑。

完整代码示例

以下基于 Python 内置的 sqlite3 模块演示,包含连接建立、错误写法对比及正确写法。其他驱动(如 psycopg2、pymysql)逻辑一致,仅占位符风格略有不同。

import sqlite3

# 建立连接
conn = sqlite3.connect(':memory:')
cursor = conn.cursor()

# 初始化表结构
cursor.execute('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)')
cursor.execute("INSERT INTO users (name) VALUES ('Alice')")
conn.commit()

# --- 错误写法(存在注入风险) ---
user_input = "' OR '1'='1"
# 危险:直接拼接字符串
sql_risk = "SELECT * FROM users WHERE name = '" + user_input + "'"
# cursor.execute(sql_risk)  # 执行会导致注入

# --- 正确写法(参数化查询) ---
# 安全:使用占位符 ?,sqlite3 使用问号,MySQL/PG 通常使用 %s
sql_safe = "SELECT * FROM users WHERE name = ?"
cursor.execute(sql_safe, (user_input,))

# 获取结果
result = cursor.fetchall()
print(f"查询结果数量:{len(result)}")  # 参数化后结果为 0,注入被拦截

# 关闭资源
cursor.close()
conn.close()

验证方法

1. Payload 测试

在输入框尝试提交 ' OR '1'='1'; DROP TABLE users; -- 等经典 payload。如果接口返回正常数据(即未匹配到数据)且数据库未受损,说明注入被拦截。参数化查询会将输入内容视为纯文本值,而非 SQL 指令。

2. 代码审查

确认所有 execute 调用都没有使用字符串格式化操作。如果使用 ORM(如 SQLAlchemy、Django ORM),默认情况下它们已经使用了参数化查询,但需注意 extra()raw() 等绕过机制。

Python Web 接口防止 SQL 注入攻击怎么使用参数化查询

常见注意事项

1. 表名和字段名无法参数化

参数化查询只能用于值(WHERE 后面的内容),不能用于表名、字段名或 ORDER BY 子句。这些位置如果需要动态控制,必须使用白名单校验,不能直接信任用户输入。

2. 占位符不要加引号

驱动会自动处理数据类型和转义。例如 WHERE name = ? 是正确的,而 WHERE name = '?' 会导致查询失败或类型错误。

3. 参数传递格式

确保第二个参数是序列类型(如元组或列表)。如果只有一个参数,记得加逗号构成元组,例如 (value,) 而不是 (value)

参考来源