Security: Dynamic Order By clause using sp_executeSql

  • This is a contrived example - but illustrates what I'm trying to fix

    I have a code where the client can change the sort order via a parameter.

    DECLARE @pageTop INT = 1
    DECLARE @pageBottom INT = 10
    DECLARE @OrderBy NVARCHAR(500) = 'object_id desc';

    DECLARE @SQL NVARCHAR(MAX) = '
    SELECT * ,ROW_NUMBER() OVER(ORDER BY ' + @OrderBy + ') AS [RowNo] INTO #orderedCols FROM sys.columns ORDER BY ' + @OrderBy + ';SELECT * FROM #orderedCols WHERE RowNo BETWEEN @PageTop AND @PageBottom ORDER BY RowNo
    '
    EXEC sp_executeSql @SQL, N'@PageTop INT, @PageBottom INT', @pageTop, @pageBottom

    And this is vulnerable to injection attacks as follows

    DECLARE @pageTop INT = 1
    DECLARE @pageBottom INT = 10
    DECLARE @OrderBy NVARCHAR(500) = 'name) FROM sys.database_principals;PRINT ''you have been hacked!''--'

    DECLARE @SQLUnsafe NVARCHAR(MAX) = '
    SELECT * ,ROW_NUMBER() OVER(ORDER BY ' + @OrderBy + ') AS [RowNo] INTO #orderedCols FROM sys.columns ORDER BY ' + @OrderBy + '; SELECT * FROM #orderedCols WHERE RowNo BETWEEN @PageTop AND @PageBottom ORDER BY RowNo
    '

    EXEC sp_executeSql @SQLUnsafe, N'@PageTop INT, @PageBottom INT', @pageTop, @pageBottom

    The Order by parameter cannot be included in the sp_executeSql parameters - this does not work

    DECLARE @pageTop INT = 1
    DECLARE @pageBottom INT = 10
    DECLARE @OrderBy NVARCHAR(500) = 'object_id desc';

    DECLARE @SQL_givesError NVARCHAR(MAX) = '
    SELECT * ,ROW_NUMBER() OVER(ORDER BY @OrderBy) AS [RowNo] INTO #orderedCols FROM sys.columns ORDER BY @OrderBy; SELECT * FROM #orderedCols WHERE RowNo BETWEEN @PageTop AND @PageBottom ORDER BY RowNo
    '
    EXEC sp_executeSql @SQL_givesError, N'@OrderBy NVARCHAR(500), @PageTop INT, @PageBottom INT', @OrderBy, @pageTop, @pageBottom

    Error is:

    Msg 1008, Level 16, State 1, Line 57

    The SELECT item identified by the ORDER BY number 1 contains a variable as part of the expression identifying a column position. Variables are only allowed when ordering by an expression referencing a column name.

    How can I parameterize @OrderBy to protect against the injection attack

     

  • It's a bit hacky and not as user-friendly, but if you were to create two arguments as follows

    @SortColumnCode TINYINT

    @SortOrder BIT = 0

    and then decode the SortColumnCode number to a column name inside your proc, the injection threat goes away because the datatype is enforced. But you'd have to provide a Code/Column Name list to the client.

    The absence of evidence is not evidence of absence
    - Martin Rees
    The absence of consumable DDL, sample data and desired results is, however, evidence of the absence of my response
    - Phil Parkin

  • Maybe a TVP w/ SortColumnName and Direction/SortOrder columns? SortOrder could be constrained to 'ASC' & 'DESC', or '+' & '-'

    With validation in the procedure that SortColumnName is actually a valid column name

  • ratbak wrote:

    --

    With validation in the procedure that SortColumnName is actually a valid column name

    I thought about this too. But this validation needs to be written carefully, also to avoid the possibility of injection.

    The absence of evidence is not evidence of absence
    - Martin Rees
    The absence of consumable DDL, sample data and desired results is, however, evidence of the absence of my response
    - Phil Parkin

  • For completeness: code for Phil's solution

    public enum SortColumn (
    Name = 1,
    Object_Id = 2
    )
    public enum SortDirection (
    Ascending = 0,
    Descending = 1
    )
    public class SortSubClause (
    public SortColumn SortColumn {get; set;}
    public SortDirection SortDirection {get; set;} = SortDirection.Ascending
    )
    public class SortOrder (
    public IEnumerable<SortSubClause> SortClauses {get; set;}
    )
    // build TVP type udt_SortOrder from SortOrder
    CREATE TYPE udt_SortOrder AS TABLE (
    SortColumnId TINYINT NOT NULL,
    SortDirection BIT NOT NULL
    )

    -- in the PROC add parameter udt_SortOrder

    -- example
    DECLARE @SortClauses [udt_SortOrder]
    INSERT INTO @SortClauses (SortColumnId, SortDirection) VALUES
    (1,1) ,(2,0)

    DECLARE @OrderBy NVARCHAR(MAX);
    SELECT @OrderBy = STUFF ((
    SELECT ', ' + CASE SortColumnId WHEN 1 THEN '[Name]'
    WHEN 2 THEN '[object_id]'
    END + ' ' +
    CASE WHEN SortDirection = 1 THEN 'DESC' ELSE '' END
    FROM @SortClauses
    FOR XML PATH ('') ),1,2,'')
    SELECT @OrderBy

    Advantage - deals with the injection threat - and client can only use valid sort parameters.

    But the disadvantage is maintenance in two places

     

  • Curious, why not create 2, 4, or ?? procs instead?  Instead of enums in C#, a UDT, and dynamic SQL could you not get rid of the parameterization and just create a proc for each situation?

    Aus dem Paradies, das Cantor uns geschaffen, soll uns niemand vertreiben können

  • unless the client is able to do their own sql, the list of possible columns, or even formulas used, would be available upfront on the UI - so allowing them to select for a list of possible column from within the output list, and specifying if ASC/DESC should be trivial.

    that output number of the column (from output list) would then be the one supplied to the SP, or even inserted into a session table for use within the SP. Using the column ordinal instead of the name would make it trivial to validate data as only valid values would be either a number or ASC/DESC.

  • This was removed by the editor as SPAM

Viewing 8 posts - 1 through 7 (of 7 total)

You must be logged in to reply to this topic. Login to reply