flawopen.com/SQL 注入/存储过程
存储过程能防止 SQL 注入吗?
参考页面
简要回答
不能——这是常见误解。只有在存储过程内部绑定参数时,它才有防护作用。如果它把输入拼接成查询字符串,再通过 EXEC 或 sp_executesql 执行,其风险与应用层字符串拼接相同。
不安全 — 将值拼接进动态 SQL
-- SQL Server T-SQL; dbo.Users.Id is NVARCHAR(50).
CREATE PROCEDURE dbo.GetUser
@UserId NVARCHAR(50)
AS
BEGIN
DECLARE @sql NVARCHAR(MAX) =
N'SELECT Id FROM dbo.Users WHERE Id = N'''
+ @UserId + N'''';
EXEC(@sql);
END;
安全 — 使用 sp_executesql 绑定参数
-- SQL Server T-SQL; same illustrative schema.
CREATE PROCEDURE dbo.GetUser
@UserId NVARCHAR(50)
AS
BEGIN
DECLARE @sql NVARCHAR(MAX) =
N'SELECT Id FROM dbo.Users WHERE Id = @id';
EXEC sys.sp_executesql
@stmt = @sql,
@params = N'@id NVARCHAR(50)',
@id = @UserId;
END;
这种误解为何常见
“使用存储过程”常被当作 SQL 注入的通用修复方案。只有当过程在普通查询中把参数作为绑定值传入时,这才成立。如果它使用 EXEC 构造动态 SQL 并拼接不可信值,就会重新引入相同风险。必须使用动态 SQL 时,应通过 sp_executesql 绑定值。表名和列名不能作为值绑定;应通过严格白名单将允许选项映射到固定标识符。
常见问题
sp_executesql 是否也能安全使用?
可以。sp_executesql 支持参数:先声明参数,再单独传入值(例如 EXEC sp_executesql @sql, N'@id INT', @id = @UserId)。不要把值拼进 @sql。动态表名或列名应使用严格白名单,因为标识符不能作为绑定参数。