6

パラメータ化されたコマンドを使用してデータベースにクエリを実行する適切な方法をチェックしていましたが、汎用 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
4

2 に答える 2

2

これが私がこれまでに持っているものです。columnNames (後で説明します) を除いて、sp_executesql を使用してパラメーター化されていない各列をテストしました。

最終的に columnNames をテストした方法に完全に満足しているわけではありませんが、これが本当に問題だとは思いません。まず最初に、columnList がユーザーではなくアプリケーションから入ってくることを期待します。ユーザーは、最初の 7 つのパラメーターを入力するためにデータベースの構造について十分に知っているべきではなく、開発者は SQL インジェクションよりもはるかに多くのリスクを抱えています。そうは言っても、columnListが配置されている場所を考えると機能する唯一のSQLインジェクションは、両側にスペースを含む「FROM」という単語を含める必要があります。そのため、それをテストし、発生した場合はエラーを発生させました。「この列は他の場所からのものです」という名前の列がある場合、これは問題になりますが、そうでないことを願っています。

ただのメモ。sys.all_objectsシステム テーブルがプロシージャによってクエリされる場合に備えて、テストにはおよびsys.all_columnsシステム ビューを使用しました。それらをより一般的に使用されるものに変更するsys.objectssys.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
于 2013-07-10T04:16:42.143 に答える
1

本当にこの道を進みたいなら、ここが出発点です。無料の文字列分割機能が含まれています。またNULL、空の文字列ではなく s を使用して、未使用のフィルター列を示します。

create function dbo.SplitCSL( @CSL as NVarChar(4000) )
  -- Based on Jeff Moden's design.
  returns table
  with schemabinding as
  return
  with Digits as ( select Digit from ( values (0), (1), (2), (3), (4), (5), (6), (7), (8), (9) ) as Digits( Digit ) ),
    Numbers as ( select ( ( Ten_3.Digit * 10 + Ten_2.Digit ) * 10 + Ten_1.Digit ) * 10 + Ten_0.Digit + 1 as Number
      from Digits as Ten_0 cross join Digits as Ten_1 cross join Digits as Ten_2 cross join Digits as Ten_3 ),
    cteTally(N) as ( select 0 union all select top ( DataLength( IsNull( @CSL, 1 ) ) ) 
      Row_Number() over ( order by ( select NULL ) ) from Numbers ),
    cteStart(N1) as ( select N + 1 from cteTally where Substring( @CSL, N, 1 ) = N',' OR N = 0 )
  select Item = Substring( @CSL, N1, IsNull( NullIf( CharIndex( N',', @CSL, N1 ), 0 ) - N1, 8000 ) )
    from cteStart;
go

create procedure [dbo].[SafeSqlSP_SelectGivenTableWithOptionalColFilter] 
  @ColumnList VarChar(75) = '*',
  @TableName SysName,
  @ColNameAsFilter1 VarChar(75) = NULL,
  @ColNameAsFilter2 VarChar(75) = NULL,
  @ColFilter1Val VarChar(75) = '',
  @ColFilter2Val VarChar(75) = ''       
as
  begin

  set nocount on;

  declare @Schema as SysName = 'dbo';

  -- Validate the table name.
  if not exists ( select 42 from Information_Schema.Tables where Table_Schema = @Schema and Table_Name = @TableName )
    begin
    RaIsError( '@TableName   does not reference a valid table or view.', 13, 0 );
    return;
    end;

  -- Validate and   QUOTENAME   the column list.
  declare @SelectList as NVarChar(1000);
  if @ColumnList = '*'
    set @SelectList = '*'
  else
    begin
    declare @Columns as Table ( ColumnName SysName );
    insert into @Columns
      select Item from dbo.SplitCSL( @ColumnList );
    if exists ( select 42 from @Columns as C left outer join
      Information_Schema.Columns as ISC on ISC.Table_Schema = @Schema and ISC.Table_Name = @TableName and ISC.Column_Name = C.ColumnName
      where ISC.Column_Name is NULL )
      begin
      RaIsError( '@ColumnList   references an invalid column in the table or view.', 13, 0 );
      return;
      end;
    -- Build a comma separated list ofquoted column names.
    select @SelectList = Stuff(
      ( select N',' + QuoteName( ColumnName ) from @Columns for XML path(''), type).value('.[1]', 'NVarChar(1000)' ),
      1, 1, '' );
    end;

  -- Validate the filter columns.
  if @ColNameAsFilter1 is not NULL and
    not exists ( select 42 from Information_Schema.Columns
      where Table_Schema = @Schema and Table_Name = @TableName and Column_Name = @ColNameAsFilter1 )
    begin
    RaIsError( '@ColNameAsFilter1   does not reference a valid column in the table or view.', 13, 0 );
    return;
    end;
  if @ColNameAsFilter2 is not NULL and
    not exists ( select 42 from Information_Schema.Columns
      where Table_Schema = @Schema and Table_Name = @TableName and Column_Name = @ColNameAsFilter2 )
    begin
    RaIsError( '@ColNameAsFilter2   does not reference a valid column in the table or view.', 13, 0 );
    return;
    end;

  -- Build the parameterized query.
  declare @SqlCommand as NVarChar(1000) =
    'select ' + @SelectList + ' from ' + QuoteName( @Schema ) + '.' + QuoteName( @TableName ) + case
    when @ColNameAsFilter1 is not NULL and @ColNameAsFilter2 is NULL
      then ' where ' + QuoteName( @ColNameAsFilter1 ) + ' = @ColFilter1Val'
    when @ColNameAsFilter1 is NULL and @ColNameAsFilter2 is not NULL
      then ' where ' + QuoteName( @ColNameAsFilter2 ) + ' = @ColFilter2Val'
    when @ColNameAsFilter1 is not NULL and @ColNameAsFilter2 is not NULL
      then ' where ' + QuoteName( @ColNameAsFilter1 ) + ' = @ColFilter1Val and ' + QuoteName( @ColNameAsFilter2 ) + ' = @ColFilter2Val'
    else ''
    end

  print 'SQL: ' + @SqlCommand; -- Debugging.

  declare @ParameterDefinitions as NVarChar(1000) = '@ColFilter1Val VarChar(75), @ColFilter2Val VarChar(75)';
  execute sp_ExecuteSql @SqlCommand, @ParameterDefinitions, @ColFilter1Val, @ColFilter2Val;

  end
go

プロシージャを呼び出す C# コードは、次のようになります。

using (SqlConnection sqlConnection = new SqlConnection(yourConnectionString))
{
    // Connect to the database.
    sqlConnection.Open();

    using (SqlCommand sqlCommand = new SqlCommand())
    {
        // Set up the stored procedure call.
        sqlCommand.Connection = sqlConnection;
        sqlCommand.CommandType = System.Data.CommandType.StoredProcedure;
        sqlCommand.CommandText = "SafeSqlSP_SelectGivenTableWithOptionalColFilter";
        sqlCommand.Parameters.AddWithValue("@ColumnList", "ShoeSize");
        sqlCommand.Parameters.AddWithValue("@TableName", "Employees");
        sqlCommand.Parameters.AddWithValue("@ColNameAsFilter1 ", "ManagerId");
        sqlCommand.Parameters.AddWithValue("@ColFilter1Val ", "42");

        // Execute the stored procedure.
        SqlDataReader reader = sqlCommand.ExecuteReader();

        // Process the results.
        while (reader.Read())
        {
            Debug.Print((string)reader["ShoeSize"]);
        }
        reader.Close();
    }
    sqlConnection.Close();
}
于 2013-07-10T04:03:55.587 に答える