iri*_*ias 9 security sql-server
让我们想象一个存储过程,它检索数据并进行某种分页。这个过程有一些输入,描述了我们想要的数据集以及我们如何对其进行排序。
这是一个非常简单的查询,但让我们以它为例。
create table Persons(id int, firstName varchar(50), lastName varchar(50))
go
create procedure GetPersons @pageNumber int = 1, @pageSize int = 20, @orderBy varchar(50) = 'id', @orderDir varchar(4) = 'desc'
as
declare @sql varchar(max)
set @sql = 'select id, firstName, lastName
from (
select id, firstName, LastName, row_number() over(order by '+@orderBy+' '+@orderDir+') as rn
from Persons
) t
where rn > ('+cast(@pageNumber as varchar)+'-1) * '+cast(@pageSize as varchar)+'
and rn <= '+cast(@pageNumber as varchar)+' * '+cast(@pageSize as varchar)+'
order by '+@orderBy+' '+@orderDir
exec(@sql)
Run Code Online (Sandbox Code Playgroud)
它应该像这样使用:
exec GetPersons @pageNumber = 1, @pageSize = 20, @orderBy = 'id', @orderDir = 'desc'
Run Code Online (Sandbox Code Playgroud)
但是一个聪明的人可以启动:
exec GetPersons @pageNumber = 1, @pageSize = 20, @orderBy = 'id)a from Persons)t;delete from Persons;print''', @orderDir = ''
Run Code Online (Sandbox Code Playgroud)
...并删除数据
这显然不是一个安全的情况。我们怎样才能防止它?
注意:这个问题不是关于“它是一种分页的好方法吗?” 也不是“做动态 sql 是件好事吗?”。问题是在动态构建 sql 查询时防止代码注入,以便在我们将来必须再次执行类似的存储过程时,有一些指导方针使代码更清晰。
一些基本思想:
验证输入
create procedure GetPersons @pageNumber int = 1, @pageSize int = 20, @orderBy varchar(50) = 'id', @orderDir varchar(4) = 'desc'
as
if @orderDir not in ('asc', 'desc') or @orderBy not in ('id', 'firstName', 'lastName')
begin
raiserror('Cheater!', 16,1)
return
end
declare @sql varchar(max)
set @sql = 'select id, firstName, lastName
from (
select id, firstName, LastName, row_number() over(order by '+@orderBy+' '+@orderDir+') as rn
from Persons
) t
where rn > ('+cast(@pageNumber as varchar)+'-1) * '+cast(@pageSize as varchar)+'
and rn <= '+cast(@pageNumber as varchar)+' * '+cast(@pageSize as varchar)+'
order by '+@orderBy+' '+@orderDir
exec(@sql)
Run Code Online (Sandbox Code Playgroud)
传递 ids 而不是字符串作为输入
create procedure GetPersons @pageNumber int = 1, @pageSize int = 20, @orderBy tinyint = 1, @orderDir bit = 0
as
declare @orderByName varchar(50)
set @orderByName = case @orderBy when 1 then 'id'
when 2 then 'firstName'
when 3 then 'lastName'
end
+' '+case @orderDir
when 0 then 'desc'
else 'asc'
end
if @orderByName is null
begin
raiserror('Cheater!', 16,1)
return
end
declare @sql varchar(max)
set @sql = 'select id, firstName, lastName
from (
select id, firstName, LastName, row_number() over(order by '+@orderByName+') as rn
from Persons
) t
where rn > ('+cast(@pageNumber as varchar)+'-1) * '+cast(@pageSize as varchar)+'
and rn <= '+cast(@pageNumber as varchar)+' * '+cast(@pageSize as varchar)+'
order by '+@orderByName
exec(@sql)
Run Code Online (Sandbox Code Playgroud)
还有其他建议吗?
AMt*_*two 11
在您的示例代码中,您将三类“事物”传递到动态 SQL 中。
ASC
或的关键字DESC
。@PageNumber
and @PageSize
,它成为生成的字符串中的文字。这真的很简单——您只想验证您的输入。您很清楚这是该选项的正确选择。在这种情况下,您期望ASC
或DESC
,因此您可以检查用户是否传递了这些值之一,或者您可以切换到不同的参数语义,其中您有一个参数是切换开关。将您的参数声明为@SortAscending bit = 0
,然后在您的存储过程中,将该位转换为ASC
或DESC
。
在这里,您应该使用该QUOTENAME
功能。Quotename 将确保对象得到正确 [quoted],确保如果有人试图传入“; TRUNCATE TABLE USERS”的“列”,它将被视为列名,而不是任意一段注入代码。这将失败,而不是截断USERS
表:
SELECT [; TRUNCATE TABLE USERS]...
FROM...
Run Code Online (Sandbox Code Playgroud)
对于@PageNumber
and @PageSize
,您应该使用它sp_executesql
来正确传递参数。正确参数化动态SQL可以让你不仅传递价值,而且还得到值回了。
在此示例中,@x
和@y
将是作用域为您的存储过程的变量。它们在您的动态 SQL 中不可用,因此您将它们传递到@a
和@b
,它们的范围限定为动态 SQL。这允许您在动态 SQL 内部和外部拥有正确类型的值。
DECLARE @i int,
@x int,
@y int,
@sql nvarchar(1000),
@params nvarchar(1000);
SET @x = 10;
SET @y = 5;
SET @params = N'@i_out int OUT, @a int, @b int';
SET @sql = N'SELECT @i_out = @a + @b';
EXEC sp_executesql @sql, @params, @i_out = @i OUT, @a = @x, @b = @y;
SELECT @i;
Run Code Online (Sandbox Code Playgroud)
即使使用 varchar 值,将值保留为变量也可以防止有人随意传递要执行的代码。此示例确保用户输入得到SELECT
ed,而不是任意执行:
DECLARE @UserInput varchar(100),
@params nvarchar(1000) = N'@value varchar(100)',
@sql nvarchar(1000) = N'SELECT Value = @value';
SET @UserInput = '; TRUNCATE TABLE USERS;'
EXEC sp_executesql @sql, @params, @value = @UserInput;
Run Code Online (Sandbox Code Playgroud)
这是我的存储过程版本,带有表定义和一些示例行:
CREATE TABLE dbo.Persons
(
id INT,
firstName VARCHAR(50),
lastName VARCHAR(50)
);
GO
INSERT INTO dbo.Persons(id, firstName,lastName)
VALUES (1,'George','Washington'),
(2,'John','Adams'),
(3,'Thomas','Jefferson'),
(4,'James','Madison'),
(5,'James','Monroe')
ALTER PROCEDURE dbo.GetPersons
@pageNumber INT = 1,
@pageSize INT = 20,
@orderBy VARCHAR(50) = 'id',
@orderDir VARCHAR(4) = 'desc'
AS
SET NOCOUNT ON;
--validate inputs
IF NOT EXISTS ( SELECT 1 FROM sys.columns
WHERE object_id = OBJECT_ID('dbo.Persons')
AND name = @orderBy )
BEGIN
RAISERROR('Order by column does not exist.', 16,1);
RETURN;
END;
IF (@orderDir NOT IN ('ASC', 'DESC'))
BEGIN
RAISERROR('Order direction is invalid. Must be ASC or DESC.', 16,1);
RETURN;
END;
--Now do stuff
--sp_executesql takes in nvarchar as a datatype
DECLARE @sql NVARCHAR(MAX);
SET @sql = N'SELECT id, firstName, lastName
FROM (
SELECT id, firstName, LastName, ROW_NUMBER() OVER(ORDER BY '
+ QUOTENAME(@orderBy) + N' ' + @orderDir + N') AS rn
FROM dbo.Persons
) t
WHERE rn > ( @pageNumber-1) * @pageSize
AND rn <= @pageNumber * @pageSize
ORDER BY ' + QUOTENAME(@orderBy) + N' ' + @orderDir;
EXEC sys.sp_executesql @sql, N'@pageNumber int, @pageSize int',
@pageNumber = @pageNumber, @pageSize = @pageSize;
GO
Run Code Online (Sandbox Code Playgroud)
你可以在这里看到,代码是功能性的,并为你提供了正确的排序和分页:
EXEC dbo.GetPersons @OrderBy = 'id', @orderDir = 'DESC';
EXEC dbo.GetPersons @OrderBy = 'id', @orderDir = 'ASC';
EXEC dbo.GetPersons @OrderBy = 'firstName';
EXEC dbo.GetPersons @OrderBy = 'lastName';
EXEC dbo.GetPersons @PageNumber = 2, @PageSize = 1, @OrderBy = 'lastName', @orderDir = 'ASC';
Run Code Online (Sandbox Code Playgroud)
还要看看输入处理如何防止有人试图做奇怪的事情:
EXEC dbo.GetPersons @OrderBy = 'lastName', @orderDir = 'UP';
EXEC dbo.GetPersons @OrderBy = ';TRUNCATE TABLE Persons;';
Run Code Online (Sandbox Code Playgroud)
Aaron Bertrand 的坏习惯:使用 EXEC() 而不是 sp_executesql
归档时间: |
|
查看次数: |
11204 次 |
最近记录: |