如何在使用sp_executesql尝试安全的通用存储过程时返回select的结果

Avi*_*fer 5 sql-server stored-procedures sql-injection sql-server-2012

我正在通过使用参数化命令检查查询数据库的正确方法,并且仍然能够将与任何表匹配的查询作为通用SP.

这个sql sp似乎没有为SQL注入打开一个漏洞,如果我错了,请纠正我,这是安全的......

但问题是,使用sp_executesql(似乎是安全的关键元素)不会返回SELECT结果.

我怎么能改变那个存储过程来返回值(而不是'破坏'它的安全性)

    CREATE PROCEDURE [dbo].[SafeSqlSP_SelectGivenTableWithOptionalColFilter] 
    @columnList varchar(75) ='*',
    @tableName sysname ,
    @ColNameAsFilter1 varchar(75) ='',
    @ColNameAsFilter2 varchar(75) ='',
    @ColFilter1VAL varchar(75)='',
    @ColFilter2VAL varchar(75)=''       
    AS
            BEGIN
        SET NOCOUNT ON;

DECLARE @sqlCommand nvarchar(1000)
        if( @ColNameAsFilter2!='' AND @ColNameAsFilter1!='')
            begin
                SET @sqlCommand = 'SELECT ' + QUOTENAME(@columnList) + ' FROM ' + QUOTENAME(@tableName) +' WHERE ' + QUOTENAME(@ColNameAsFilter1) +' = @ColFilter1VAL AND ' + QUOTENAME(@ColNameAsFilter2) +' = @ColFilter2VAL'
                EXECUTE sp_executesql @sqlCommand,
                N'@ColFilter1VAL nvarchar(75), @ColFilter2VAL nvarchar(75)', @ColFilter1VAL= @ColFilter1VAL, @ColFilter2VAL = @ColFilter2VAL
            end
        else if( @ColNameAsFilter1!='' AND @ColNameAsFilter2='') 
            begin
                SET @sqlCommand = 'SELECT ' + QUOTENAME(@columnList) + ' FROM ' + QUOTENAME(@tableName) +' WHERE ' + QUOTENAME(@ColNameAsFilter1) +' = @ColFilter1VAL'
                EXECUTE sp_executesql @sqlCommand,
                N'@ColFilter1VAL nvarchar(75)', @ColFilter1VAL= @ColFilter1VAL
            end
        else if( @ColNameAsFilter1='' AND @ColNameAsFilter2='') 
            begin
                SET @sqlCommand = 'SELECT ' + QUOTENAME(@columnList) + ' FROM ' + QUOTENAME(@tableName)
                EXECUTE sp_executesql @sqlCommand
            end
    END
Run Code Online (Sandbox Code Playgroud)

Ken*_*her 2

这是我到目前为止所拥有的。除了columnNames(我将在稍后介绍)之外,我已经测试了未使用sp_executesql 进行参数化的每个列。

我对最终测试 columnNames 的方式并不完全满意,但我认为这并不是真正的问题。首先,我希望 columnList 来自应用程序,而不是用户。用户不应该对数据库的结构有足够的了解来填写前 7 个参数,并且开发人员面临的风险比 SQL 注入要大得多。话虽这么说,我能想到的唯一可行的 SQL 注入是在放置 columnList 的地方必须包含单词“FROM”,并且其两侧各有一个空格。所以我对此进行了测试,如果发生则提出错误。如果您碰巧有一个名为“此列来自其他地方”的列,这将是一个问题,但我们希望您没有。

只是一个注释。我使用sys.all_objectssys.all_columns系统视图进行测试,以防过程查询系统表。如果将它们更改为更常用的存储过程sys.objects,则sys.columns存储过程将不适用于系统视图。

编辑:我在 columnList 上添加了一个测试来检查分号。我意识到类似的事情'''abc''; print ''test''; select * '会成为一个问题。

EDIT2:我已经更新了程序以更好地处理列列表。谢谢@HABO!我还添加了代码来处理值是否以QUOTENAME[]、" 或 ' 开头,以及列列表中 * 的一些常见格式。我正在使用 @HABO 列出的拆分器函数,所以我我就不在这里列出了。

ALTER PROCEDURE [dbo].[SafeSqlSP_SelectGivenTableWithOptionalColFilter] 
    @columnList nvarchar(max) ='*',
    @tableSchema sysname ,
    @tableName sysname ,
    @ColNameAsFilter1 nvarchar(255) ='',
    @ColNameAsFilter2 nvarchar(255) ='',
    @ColFilter1VAL nvarchar(max)='',
    @ColFilter2VAL nvarchar(max)=''       
    AS

BEGIN
    SET NOCOUNT ON;

    --====================================================
    -- Set default values
    IF ISNULL(@tableSchema,'') = ''
        SET @tableSchema = 'dbo'
    ELSE
        SET @tableSchema = LTRIM(RTRIM(@tableSchema))

    IF ISNULL(@columnList,'') = ''
        SET @columnList = '*'

    SET @tableName = ISNULL(LTRIM(RTRIM(@tableName)),'')
    SET @ColNameAsFilter1 = ISNULL(LTRIM(RTRIM(@ColNameAsFilter1)),'')
    SET @ColNameAsFilter2 = ISNULL(LTRIM(RTRIM(@ColNameAsFilter2)),'')
    SET @ColFilter1VAL = ISNULL(@ColFilter1VAL,'')
    SET @ColFilter2VAL = ISNULL(@ColFilter2VAL,'')

    --====================================================
    -- Remove probably QUOTENAMEs from @tableSchema and @tableName before testing them
    SET @tableSchema = CASE WHEN LEFT(@tableSchema,1) = '[' AND RIGHT(@tableSchema,1) = ']'
                            THEN SUBSTRING(REPLACE(@tableSchema,']]',']'),2,LEN(REPLACE(@tableSchema,']]',']'))-2)
                        WHEN LEFT(@tableSchema,1) = '"' AND RIGHT(@tableSchema,1) = '"'
                            THEN SUBSTRING(REPLACE(@tableSchema,'""','"'),2,LEN(REPLACE(@tableSchema,'""','"'))-2)
                        WHEN LEFT(@tableSchema,1) = '''' AND RIGHT(@tableSchema,1) = ''''
                            THEN SUBSTRING(REPLACE(@tableSchema,'''''',''''),2,LEN(REPLACE(@tableSchema,'''''',''''))-2)
                        ELSE @tableSchema END

    SET @tableName = CASE WHEN LEFT(@tableName,1) = '[' AND RIGHT(@tableName,1) = ']'
                            THEN SUBSTRING(REPLACE(@tableName,']]',']'),2,LEN(REPLACE(@tableName,']]',']'))-2)
                        WHEN LEFT(@tableName,1) = '"' AND RIGHT(@tableName,1) = '"'
                            THEN SUBSTRING(REPLACE(@tableName,'""','"'),2,LEN(REPLACE(@tableName,'""','"'))-2)
                        WHEN LEFT(@tableName,1) = '''' AND RIGHT(@tableName,1) = ''''
                            THEN SUBSTRING(REPLACE(@tableName,'''''',''''),2,LEN(REPLACE(@tableName,'''''',''''))-2)
                        ELSE @tableName END

    --====================================================
    -- Test to make sure the schema.table exists
    IF NOT EXISTS (
                    SELECT 1 
                    FROM sys.all_objects
                    JOIN sys.schemas
                        ON sys.all_objects.schema_id = sys.schemas.schema_id
                    WHERE sys.all_objects.name = @tableName
                      AND sys.schemas.name = @tableSchema
                      AND sys.all_objects.[TYPE] IN ('S','U','V')
                    )
        BEGIN
            RAISERROR (N'Table %s.%s does not exist.',
                        16,
                        1,
                        @tableSchema,
                        @tableName)
            RETURN
        END

    --====================================================
    -- Test to make sure all of the comma delimited values 
    -- are valid columns for schema.table

    -- Create and populate a list of columns from columnlist
    DECLARE @ColumnListTable TABLE (Item varchar(255))

    INSERT INTO @ColumnListTable
    SELECT Item
    FROM dbo.SplitCSL(@columnList)

    -- Remove any extra spaces
    UPDATE @ColumnListTable SET Item = LTRIM(RTRIM(Item))
    -- "Fix" any * formats to a single format of [schema].[tablename].*
    UPDATE @ColumnListTable SET Item = CASE WHEN Item IN (

                @tableName + '.*',  @tableName + '.[*]', 
                '[' + @tableName + '].*', '[' + @tableName + '].[*]',

                @tableSchema + '.' + @tableName + '.*',  @tableSchema + '.' + @tableName + '.[*]', 
                @tableSchema + '.' + '[' + @tableName + '].*', @tableSchema + '.' + '[' + @tableName + '].[*]',

                '[' + @tableSchema + '].' + @tableName + '.*',  '[' + @tableSchema + '].' + @tableName + '.[*]', 
                '[' + @tableSchema + '].' + '[' + @tableName + '].*', '[' + @tableSchema + '].' + '[' + @tableName + '].[*]'
            ) 
            THEN '[' + @tableSchema + '].' + '[' + @tableName + '].*'
            WHEN Item IN ('*','[*]') THEN '*'
            ELSE Item END

    --====================================================
    -- Remove probably QUOTENAMEs from columns in column list before testing them
    UPDATE @ColumnListTable SET Item = 
                        CASE WHEN LEFT(Item,1) = '[' AND RIGHT(Item,1) = ']'
                            THEN SUBSTRING(REPLACE(Item,']]',']'),2,LEN(REPLACE(Item,']]',']'))-2)
                        WHEN LEFT(Item,1) = '"' AND RIGHT(Item,1) = '"'
                            THEN SUBSTRING(REPLACE(Item,'""','"'),2,LEN(REPLACE(Item,'""','"'))-2)
                        WHEN LEFT(Item,1) = '''' AND RIGHT(Item,1) = ''''
                            THEN SUBSTRING(REPLACE(Item,'''''',''''),2,LEN(REPLACE(Item,'''''',''''))-2)
                        ELSE Item END

    -- Check for invalid column names
    DECLARE @ColumnListFailures AS varchar(max)
    SET @ColumnListFailures = ''

    SELECT @ColumnListFailures = STUFF((    
        SELECT ', ' + Item
        FROM @ColumnListTable
        WHERE Item NOT IN (SELECT name
                            FROM sys.all_columns
                            WHERE object_id = OBJECT_ID(@tableSchema+'.'+@tableName))
          AND Item <> '[' + @tableSchema + '].' + '[' + @tableName + '].*'
        FOR XML PATH(''),TYPE).value('.','VARCHAR(MAX)')
        ,1,2, '')

    IF LEN(@ColumnListFailures) > 0
    BEGIN
            RAISERROR (N'Table %s.%s does not have columns %s that are listed in the columnList parameter.',
                        16,
                        1,
                        @tableSchema,
                        @tableName,
                        @ColumnListFailures)
            RETURN
    END

    -- QUOTENAME each of the column names and re-create @ColumnList
    SELECT @ColumnList = STUFF((
        SELECT ', ' + CASE WHEN Item = '[' + @tableSchema + '].' + '[' + @tableName + '].*' THEN Item 
                ELSE QUOTENAME(Item) END
        FROM @ColumnListTable
        FOR XML PATH(''),TYPE).value('.','VARCHAR(MAX)')
        ,1,2, '')


    --====================================================
    -- Remove probably QUOTENAMEs from first and second column filters before testing them
    SET @ColNameAsFilter1 = CASE WHEN LEFT(@ColNameAsFilter1,1) = '[' AND RIGHT(@ColNameAsFilter1,1) = ']'
                            THEN SUBSTRING(REPLACE(@ColNameAsFilter1,']]',']'),2,LEN(REPLACE(@ColNameAsFilter1,']]',']'))-2)
                        WHEN LEFT(@ColNameAsFilter1,1) = '"' AND RIGHT(@ColNameAsFilter1,1) = '"'
                            THEN SUBSTRING(REPLACE(@ColNameAsFilter1,'""','"'),2,LEN(REPLACE(@ColNameAsFilter1,'""','"'))-2)
                        WHEN LEFT(@ColNameAsFilter1,1) = '''' AND RIGHT(@ColNameAsFilter1,1) = ''''
                            THEN SUBSTRING(REPLACE(@ColNameAsFilter1,'''''',''''),2,LEN(REPLACE(@ColNameAsFilter1,'''''',''''))-2)
                        ELSE @ColNameAsFilter1 END

    SET @ColNameAsFilter2 = CASE WHEN LEFT(@ColNameAsFilter2,1) = '[' AND RIGHT(@ColNameAsFilter2,1) = ']'
                            THEN SUBSTRING(REPLACE(@ColNameAsFilter2,']]',']'),2,LEN(REPLACE(@ColNameAsFilter2,']]',']'))-2)
                        WHEN LEFT(@ColNameAsFilter2,1) = '"' AND RIGHT(@ColNameAsFilter2,1) = '"'
                            THEN SUBSTRING(REPLACE(@ColNameAsFilter2,'""','"'),2,LEN(REPLACE(@ColNameAsFilter2,'""','"'))-2)
                        WHEN LEFT(@ColNameAsFilter2,1) = '''' AND RIGHT(@ColNameAsFilter2,1) = ''''
                            THEN SUBSTRING(REPLACE(@ColNameAsFilter2,'''''',''''),2,LEN(REPLACE(@ColNameAsFilter2,'''''',''''))-2)
                        ELSE @ColNameAsFilter2 END

    --====================================================
    -- Check that the first filter column name is valid
    IF @ColNameAsFilter1 <> '' AND
        NOT EXISTS (SELECT 1 
                    FROM sys.all_columns
                    WHERE object_id = OBJECT_ID(@tableSchema+'.'+@tableName)
                      AND name = @ColNameAsFilter1)
        BEGIN
            RAISERROR (N'Table %s.%s does not have a column %s.',
                        16,
                        1,
                        @tableSchema,
                        @tableName,
                        @ColNameAsFilter1)
            RETURN
        END

    --====================================================
    -- Check that the second filter column name is valid
    IF @ColNameAsFilter2 <> '' AND
        NOT EXISTS (SELECT 1 
                    FROM sys.all_columns
                    WHERE object_id = OBJECT_ID(@tableSchema+'.'+@tableName)
                      AND name = @ColNameAsFilter2)
        BEGIN
            RAISERROR (N'Table %s.%s does not have a column %s.',
                        16,
                        1,
                        @tableSchema,
                        @tableName,
                        @ColNameAsFilter2)
            RETURN
        END 


    --====================================================
    -- Construct & execute the dynamic SQL
    DECLARE @sqlCommand nvarchar(max)

    SET @sqlCommand = 'SELECT ' + @columnList + CHAR(13) +
        ' FROM [' + @tableSchema + '].['+ @tableName + ']' + CHAR(13) + 
        ' WHERE 1=1 '

    IF @ColNameAsFilter1 != ''
        SET @sqlCommand = @sqlCommand + CHAR(13) + 
            ' AND ' + QUOTENAME(@ColNameAsFilter1) + ' = @ColFilter1VAL'

    IF @ColNameAsFilter2 != ''
        SET @sqlCommand = @sqlCommand + CHAR(13) + 
            ' AND ' + QUOTENAME(@ColNameAsFilter2) + ' = @ColFilter2VAL'

    EXECUTE sp_executesql @sqlCommand,
            N'@ColFilter1VAL nvarchar(75), @ColFilter2VAL nvarchar(75)', 
            @ColFilter1VAL, @ColFilter2VAL
END
Run Code Online (Sandbox Code Playgroud)