使用动态列保护动态 SQL Where 子句

Securing Dynamic SQL Where Clause with Dynamic Column

提问人:Viki Theolorado 提问时间:8/1/2018 最后编辑:marc_sViki Theolorado 更新时间:8/1/2018 访问量:837

问:

我正在处理一个项目,我需要创建一个动态网页,该网页由一个表组成,该表的列结构和数据取决于存储过程选择结果。

下面是要使用的存储过程的示例:

CREATE PROCEDURE dbo.usp_CustomerView
    @Address VARCHAR(1000)
AS
    SELECT *
    FROM Customer
    WHERE CustomerAddress LIKE '%' + @Address + '%'

当用户筛选表中的地址列时,此过程正在起作用。

问题是,这个过程是静态的。如果我想过滤其他字段,例如 或 ,我需要创建另一个参数并像这样重新制作过程。CustomerNameCustomerPhone

CREATE PROCEDURE dbo.usp_CustomerView
    @Address VARCHAR(1000),
    @Name VARCHAR(1000),
    @Phone VARCHAR(1000)
AS
    SELECT *
    FROM Customer
    WHERE CustomerAddress LIKE '%' + @Address + '%'
      AND CustomerName LIKE '%' + @Name + '%'
      AND CustomerPhone LIKE '%' + @Phone + '%'

我正在尝试创建一个动态过程来执行基于字符串过滤器的动态 SQL 查询,如下所示。

CREATE PROCEDURE dbo.usp_CustomerView
    @Filter VARCHAR(MAX)
AS
    DECLARE @sql VARCHAR(MAX)
    SET @sql = 'SELECT * FROM Customer WHERE ' + @Filter
    EXEC @sql

然后像这样从我的网站动态生成一个字符串过滤器。

Dim filterString As String = ""
For Each filter As DataFilter In e.Filter   'Here Filter give Array of filtered user input
    If Not String.IsNullOrEmpty(filterString) Then filterString &= " AND "
    filterString &= filter.Property & " = '" & filter.Value & "'"
Next
Command.CommandText = "usp_CustomerView @Filter"
Command.Parameters.AddWithValue("@Filter", filterString)
'Read Return Value from Command

这是有效的,但我担心安全性,因为它可以很容易地注入。有没有人尝试过创建一个具有良好安全性的动态 where 子句?我认为这是一件很常见的事情。但我找不到任何线索。

有什么建议吗?

更多解释:

它的实现实际上更复杂。我想制作一个动态网页,其中我只在数据库中设置了一个存储过程的字符串名称,该页面将自动生成一个html表,其结构是通过执行存储过程获得的。所以我不能在页面中放置任何静态查询。

.NET SQL-Server SQL注入

评论

0赞 Alex K. 8/1/2018
构建一个安全的SQL字符串很简单,列名是固定的,根本不应该来自用户输入,只有搜索值应该。如果用户想要搜索 X,则添加一个子句,并简单地为 提供一个值。AND X = @X_PARAM@X_PARAM
0赞 Viki Theolorado 8/1/2018
@AlexK。是的,这将是安全的,但它需要我为所选表的每一列构建具有多个参数的不同过程。我想创建一个模板过程,无论选择的列如何,每个过程只有一个参数。

答:

0赞 Dan Guzman 8/1/2018 #1

请考虑使用来自受信任源(硬编码表或元数据表)的列和参数名称在应用代码中生成和执行参数化查询。在这里使用 proc 几乎没有价值。

评论

0赞 Viki Theolorado 8/1/2018
但该过程仍然需要多个参数,每个选定的列对应一个参数。无论它选择多少列,我都需要该过程只接收一个参数。
0赞 Dan Guzman 8/1/2018
@VikiTheolorado,如果我不清楚,对不起。我建议根本不使用存储过程。为什么要使用 proc?
0赞 Viki Theolorado 8/1/2018
对不起,如果我也不清楚。我编辑了这个问题以包含更多解释。希望它能澄清我的问题。
1赞 Thom A 8/1/2018 #2

这在黑暗中有点刺痛,但是表类型呢?然后,您可以在此基础上构建 your 子句。就像我说的,在黑暗中捅一刀,然而,这至少会让你走上正确的道路:WHERE

USE Sandbox;
GO

CREATE TYPE dbo.WhereClause AS TABLE (ColumnName sysname NOT NULL,
                                      ColumnValue sql_variant NOT NULL);
GO

CREATE PROC dbo.SampleProc @WhereClause WhereClause READONLY AS
BEGIN

    DECLARE @SQL nvarchar(MAX);

    SET @SQL = N'SELECT *' + NCHAR(10) + 
               N'FROM Customer'

    SET @SQL = @SQL + 
               ISNULL(NCHAR(10) +N'WHERE' +
                      STUFF((SELECT NCHAR(10) + N'  AND ' + QUOTENAME(WC.ColumnName) + N' LIKE ''%'' + ' + QUOTENAME(CONVERT(varchar(100),ColumnValue),'''') + ' + ''%'''
                             FROM @WhereClause WC
                             FOR XML PATH(N'')),1,6,N''),N'') + N';';
    PRINT @SQL; --Your best debugging friend (provided you don't go over an nvarchar(4000))
    --EXEC sp_executesql @SQL; --Uncomment this to actually run the Dynamic SQL

END

GO

DECLARE @Where WhereClause;
INSERT INTO @Where (ColumnName,
                    ColumnValue)
SELECT N'CustomerAddress', '123 test street';

INSERT INTO @Where (ColumnName,
                    ColumnValue)
SELECT N'CustomerName', 'Joe Bloggs';

INSERT INTO @Where (ColumnName,
                    ColumnValue)
SELECT N'CustomerPhone', '01234 567890';

INSERT INTO @Where (ColumnName,
                    ColumnValue)
SELECT N'CustomerDOB', CONVERT(date,'19810507');

EXEC dbo.SampleProc @Where;
/*
Returns:
SELECT *
FROM Customer
WHERE [CustomerAddress] LIKE '%' + '123 test street' + '%'
  AND [CustomerName] LIKE '%' + 'Joe Bloggs' + '%'
  AND [CustomerPhone] LIKE '%' + '01234 567890' + '%'
  AND [CustomerDOB] LIKE '%' + 'May  7 1981' + '%';

Note that the date isn't ideal. This *could* be a problem.
*/

 DELETE 
 FROM @Where;

 EXEC dbo.SampleProc @Where;
 /*
Returns:
SELECT *
FROM Customer;
*/
GO

DROP PROC dbo.SampleProc;
DROP TYPE dbo.WhereClause;
GO

评论

0赞 Viki Theolorado 8/1/2018
哇,这很有效..我试图将列值更改为类似 .它仍然如何工作。谢谢。。'''; PRINT(''INJECTED'')--'
0赞 Thom A 8/1/2018
@VikiTheolorado 对于动态 SQL 来说是一个非常有用的函数,并且经常被遗忘(或未使用)。QUOTENAME