sql注入防护核心是禁用字符串拼接,优先使用参数化查询,辅以白名单校验动态标识符、整型转换限制分页参数,并叠加最小权限、错误脱敏和禁用raw SQL。

sql注入防护核心是不拼接用户输入到sql语句中,优先使用参数化查询(预编译),辅以输入校验、最小权限和错误信息脱敏。下面结合高频场景讲清楚怎么落地。
场景一:登录验证——别用字符串拼接查账号
常见错误写法(危险!):
sql = “select * FROM users WHERE username = ‘” + user_input + “‘ AND password = ‘” + pwd_input + “‘”
攻击者输入用户名 ' OR '1'='1 就能绕过密码验证。
正确做法:
- java(JDBC)用 PreparedStatement:
String sql = "SELECT * FROM users WHERE username = ? AND password = ?";ps.setString(1, username); ps.setString(2, password);- python(sqlite3 / pymysql)用占位符 %s 或 ?,数据库驱动自动转义:
cursor.execute("SELECT * FROM users WHERE username = %s AND password = %s", (user, pwd))
场景二:搜索功能——动态字段名/表名不能参数化?那就白名单校验
比如前端传参 sort=age 或 table=orders,这些不能直接当参数填入 SQL(因为 ? 只适用于值,不适用于标识符)。
安全方案:
- 定义允许的字段名白名单:
valid_sorts = ["name", "age", "create_time"] - 收到 sort 参数后先检查是否在白名单内,不在就拒绝或默认 name
- 表名同理:
if table_name not in ["users", "orders", "products"]: raise ValueError("非法表名") - 绝对不要用 format() 或 f-string 拼接字段或表名
场景三:分页查询——offset 和 limit 是数字,也要防注入
错误示例:"SELECT * FROM news LIMIT " + limit + " OFFSET " + offset
虽然看起来是数字,但若没严格类型转换,攻击者可传 10; DROP TABLE news--(某些数据库支持多语句时极危险)。
安全做法:
- 强制转为整型并做范围限制(如最大只允许 100 条)
limit = max(1, min(100, int(request.args.get("limit", 10))))- 再用参数化方式传入(多数数据库支持 limit ? offset ?)
- mysql / postgresql 都支持:
SELECT * FROM goods LIMIT %s OFFSET %s
额外但关键的三层加固
光靠参数化还不够,建议叠加以下措施:
- 数据库账号最小权限:Web 应用连接数据库的账号,只给 SELECT/INSERT/UPDATE 权限,禁用 DROP、delete、union 等高危操作
- 关闭详细错误回显:生产环境把数据库报错(如 “You have an error in your SQL syntax”)替换成统一提示,避免泄露表结构
- 使用 ORM 时也别手写 raw SQL:Django 的
Filter(name__icontains=x)、SQLAlchemy 的query.filter(User.name.contains(x))默认安全;但session.execute("SELECT ... WHERE name = '" + x + "'")依然危险
基本上就这些。记住:参数化是底线,白名单是补充,权限和错误控制是保险。不复杂但容易忽略。