5

This can be easily injected here because the @ID param can be practically anything in this SQL statement by inputting it, however, how do you prevent this exploit?

I prefer to specifically prevent this exploit at this level rather than application level, any suggestions?

CREATE PROCEDURE [dbo].[GetDataByID]
@ID bigint,
@Table varchar(150)
AS
BEGIN

Declare @SQL Varchar(1000)

SELECT @SQL = 'SELECT * FROM ' + @Table + ' WHERE ID = ' + CONVERT(varchar,@ID)

SET NOCOUNT ON;

EXEC(@sql)  
END
1
  • 1
    Just a tiny thing that doesn't affect things much, but isn't it the @Table param that's the problem here? @ID is a bigint, so it can only be a number when you reach the point of building the dynamic SQL statement, right? Commented Dec 19, 2011 at 19:52

4 Answers 4

9

Check this page, it has a wonderful guide to dynamic sql, and the options to execute them safely

In your case it should be like this:

SELECT @SQL =  N'SELECT * FROM ' + quotename(@Table) + N' WHERE ID = @xid' 
EXEC sp_executesql @SQL, N'@xid bigint', @ID
Sign up to request clarification or add additional context in comments.

9 Comments

+1 being the first time I actually know someone whose activity I come across on StackOverflow ;)
Thank you for your response, but this statement now gives me an error when i try to execute the sp: Msg 214, Level 16, State 2, Procedure sp_executesql, Line 1 Procedure expects parameter '@statement' of type 'ntext/nchar/nvarchar'.
I did >Declare @SQL nVarchar(1000) instead and it still gives the same error response.
Quotename is not necessarily safe. Read edit below before using it.
Check the edit there, I don't have sql server in this machine so sadly I can't test it
|
1

1) create a new table that will have an identity PK and contain the string table names
2) insert all/only the valid tables you will allow in your procedure
3) use this int identity PK as the input parameter value (TableID) for the stored procedure
4) in the procedure, just look up the string value (table name) from the given identity PK and you are safe to concatenate that looked up string in your query. 5) your WHERE clause is fine since you pass in an int

Comments

0

I would recommend avoiding dynamic sql altogether. The problems are as follows:

  • The obvious injection attach scenario
  • Binary injection attacks are much smarter and can bypass traditional string escaping
  • Performance is the big one - SQL Server is designed to manage execution plans on stored procedures and they will run faster than queries that are built dynamically. If you are using dynamic sql, there is no real benefit to using a stored procedure at all. If you want flexibility in code for selecting from multiple tables, you should consider an ORM or something to make your code easier. Considering you have to pass in the table dynamically, then I would go so far as to say there is no point to a procedure like the above and a different solution is the best option. If you are just writing against SQL (ie no ORM), then code generating seperate procs would even be a better option.

NOTE QUOTENAME will NOT garantee you are injection safe. Truncation injection is still possible. Read http://msdn.microsoft.com/en-us/library/ms161953.aspx before using it.

1 Comment

Thank you for your response, but can you please post a working link about QUOTENAME?
0

Although I'd advice against dynamic sql in general, in this case I think you can get away with it by checking if the @Table variable contains a valid table name.

  • The question is if you plan on allowing schema names and/or cross-db queries, I' assuming you don't want to go out of the db (or the server) here but do allow for different schema's (AdventureWorks shows how they can be used)
  • You MIGHT want to also include views for @Table.
  • It probably would be 'nice' if you also checked if the object found actually has an ID column and thrown a 'userfriendly' error if not. Optional though.

Just putting QuoteName() around @table will NOT protect you against everything. Although a great function, it's far from perfect. IMHO your best bet would be to parse the @Table variable, check if its contents is valid and then create dynamic sql based on the obtained parts. I started out doing most of above and surprisingly there it requires a LOT of checking for something that looks as simple as this =)

CREATE PROCEDURE [dbo].[GetDataByID] ( 
                                        @ID bigint,
                                        @Table nvarchar(300)
                                      )
AS

DECLARE @sql nvarchar(max)

DECLARE @server_name sysname,
        @db_name     sysname,
        @schema_name sysname,
        @object_name sysname,
        @schema_id   int        

SELECT @server_name = ParseName(@Table, 4),
       @db_name     = ParseName(@Table, 3),
       @schema_name = ParseName(@Table, 2),
       @object_name = ParseName(@Table, 1)

IF ISNULL(@server_name, @@SERVERNAME) <> @@SERVERNAME
    BEGIN
        RaisError('Queries are restricted to this server only.', 16, 1)
        Return(-1)
    END

IF ISNULL(@db_name, DB_Name()) <> DB_Name()
    BEGIN
        RaisError('Queries are restricted to this database only.', 16, 1)
        Return(-1)
    END


IF @schema_name IS NULL
    BEGIN
        IF NOT EXISTS ( SELECT *
                          FROM sys.objects
                         WHERE name = @object_name
                           AND type IN ('U', 'V') )
            BEGIN
                RaisError('Requested @Table not found. [%s]', 16, 1, @object_name)
                Return(-1)
            END

        SELECT @sql = 'SELECT * FROM ' + QuoteName(@object_name) + ' WHERE ID = @ID'
    END
ELSE
    BEGIN

        SELECT @schema_id = Schema_id(@schema_name)

        IF @schema_id IS NULL 
            BEGIN
                RaisError('Unrecognized schema requested [%s].', 16, 1, @schema_name)
                Return(-1)
            END

        IF NOT EXISTS ( SELECT *
                          FROM sys.objects
                         WHERE name = @object_name
                           AND schema_id = @schema_id
                           AND type IN ('U', 'V') )
            BEGIN
                RaisError('Requested @Table not found. [%s].[%s]', 16, 1, @schema_name, @object_name)
                Return(-1)
            END

        SELECT @sql = 'SELECT * FROM ' + QuoteName(@schema_name) + '.' + QuoteName(@object_name) + ' WHERE ID = @ID'
    END

EXEC sp_executesql @stmt   = @sql,
                   @params = N'@ID bigint',
                   @ID     = @ID

Return(0)       

Supra compiles, but you might need to iron out some bugs as I didn't quite go as far as checking all code-paths.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.