views:

382

answers:

4

For the sake of argument, let's just say I have to create a local variable containing a SQL query that has an INSERT:

DECLARE @insert NVARCHAR(MAX) SELECT @insert = 'INSERT INTO [dbo].[' + @table + '] VALUES... EXEC (@insert)

This INSERT is also going to contain a column value:

DECLARE @insert NVARCHAR(MAX) SELECT @insert = 'INSERT INTO [dbo].[' + @table + '] VALUES (N''' + @message + ''')' EXEC (@insert)

Now, I'm obviously concerned about an injection attack, and would like to ensure that @message's value can't make @insert's value malicious or malformed as a query to EXEC.

This brings us to my question: is escaping the ' characters in @message sufficient? Are there any other characters that could appear in @message that could escape out?

Example:

DECLARE @insert NVARCHAR(MAX) SELECT @message = REPLACE(@message,'''','''''') SELECT @insert = 'INSERT INTO [dbo].[' + @table + '] VALUES (N''' + @message + ''')' EXEC (@insert)

(When I say "have to", this is because my query is in a stored procedure, and this stored procedure accepts @table, which is the destination table to INSERT into. I'm not interested in discussing my architecture or why the table to INSERT into is "dynamically" specified via a procedure parameter. Please refrain from commenting on this unless there's another way besides EXEC()ing a query to specify a table to INSERT into when then table name is received as a procedure parameter.)

+6  A: 

Use sp_executesql and the built-in quotename(). This article, The Curse and Blessings of Dynamic SQL, is pretty much the definitive reference.

Mitch Wheat
++ canonical reference
le dorfier
Apparently there's a 128-length limit to quotename(), even in 2008, since it expects a SQL identifier. The reference suggests creating a quotestring() function (http://www.sommarskog.se/dynamic_sql.html#quotestring), which does exactly the same thing as REPLACE(@variable,'''','''''').
Chris
+1  A: 

Rather than calling EXEC(@somesql), I suggest using the sp_executesql stored procedure. Specifically, this allows you to pass parameters, and the system will check that the parameters are valid.

Scott Whitlock
Yeah, unfortunately, SQL Server 2008 Express is telling me "Must declare the table variable '@my_table'." after executing that code.
Chris
I was sure I did this in SQL Server 2005, but I admit that I could just be very very tired. I apologize. I'll try this tomorrow.
Scott Whitlock
Nope, I'm wrong. I'll remove that section of the answer.
Scott Whitlock
+1  A: 

You could first query the schema information with regular T-SQL and make sure the table name exists first. This way, if it's malformed SQL, it won't execute as code. It will just be a VARCHAR table name.

DECLARE @Table AS VARCHAR(MAX)
DECLARE @Exists AS BIT

SET @Table = 'Vicious malformed dynamic SQL'

SELECT  @Exists = COUNT(TABLE_NAME) 
FROM    INFORMATION_SCHEMA.TABLES 
WHERE   TABLE_NAME = @Table

IF (@Exists = 1)
    BEGIN
    PRINT 'Table exists'
    -- Execute dynamic SQL.
    END
ELSE
    PRINT 'Invalid table'

(Or simply use IF EXISTS (SELECT ....) )

HardCode
Good thinking. I've been doing what follows. Is there any advantage of either? What I've been doing: "IF OBJECT_ID(@table,'U') IS NULL..."
Chris
A: 

Apparently there's a 128-length limit to quotename(), even in 2008 according to my test, since it expects a SQL identifier. The reference suggests creating a quotestring() function, which does the same thing as:

REPLACE(@variable,'''','''''')

Therefore I am proposing that the answer is to create a function out of the REPLACE() above, like so:

CREATE FUNCTION quotestring(@string nvarchar(MAX)) 
RETURNS nvarchar(MAX) AS
BEGIN
    RETURN(REPLACE(@string,'''',''''''))
END

...Unless I've misunderstood something.

Chris
Can you pass an nvarchar(MAX) as a parameter like that?
Scott Whitlock
Sure, why not? It's now an operating function in my dev. environment. :)
Chris