5

I am using stored procedures. In order to save time, I made some generic procedures that uses dynamic sqlin order to update. Such generic procedure is:

CREATE PROCEDURE [dbo].[SetField]
 @company_id uniqueidentifier,
 @id bigint,
 @field_code nvarchar(50),
 @value nvarchar(50)
AS
BEGIN
 DECLARE @field_name nvarchar(50)
 SET @field_name = NULL
 SELECT @field_name=field_name
 FROM dbo.FIELD_DEFINITION
 WHERE field_code=@field_code

 IF @field_name IS NOT NULL
 BEGIN

  IF @value IS NULL OR @value=''
  BEGIN
   SET @value='NULL'
  END
  ELSE
  BEGIN
   IF @field_code='START_DATE' OR @field_code='END_DATE'
   BEGIN
    SET @value = CONVERT(datetime, @value ,103)
   END
   SET @value=''''+@value+''''
  END

  DECLARE @sql nvarchar(1000)
  SET @sql = 'UPDATE dbo.TABLE '+
     'SET '+@field_name+'='+@value+' '+
     'WHERE company_id=''' + CAST(@company_id as nvarchar(36)) + ''' AND '+
     'id='+CAST(@id as nvarchar)
  EXEC(@sql)
 END
END

How can I prevent sql injection with this code?

Naor
  • 23,465
  • 48
  • 152
  • 268
  • 2
    You mean beyond the obvious answer of not using dynamic SQL in a stored procedure? – Thomas Jan 26 '11 at 20:26
  • @Thomas: Can you show me how to do the following without dynamic sql?? – Naor Jan 26 '11 at 20:31
  • Trying to generalize too much in database programming is a bad thing. Write the real procs that do what you need to do without trying to do this dynamic stuff which will cause more problems than it solves as it is nigh on to impossible to adequately test as well as risking injection. – HLGEM Jan 26 '11 at 20:35
  • @HLGEM: This procedure replaces more then 30 stored procedures and a lot of code that manage them. – Naor Jan 26 '11 at 20:49
  • 1
    I'd use the 30 stored procs personally, if they aren't dynamic – HLGEM Jan 26 '11 at 21:45

3 Answers3

6

You said:

In order to save time, I made some generic procedures that uses dynamic sql in order to update

If you'd asked first, we could have saved time and suggested this...

UPDATE
    dbo.TABLE
SET
    Field1 = CASE WHEN @field_name = 'Field1' THEN @value ELSE Field1 END,
    Field2 = CASE WHEN @field_name = 'Field2' THEN @value ELSE Field2 END,
    Field3 = CASE WHEN @field_name = 'Field3' THEN @value ELSE Field3 END,
    ...
    Fieldn = CASE WHEN @field_name = 'Fieldn' THEN @value ELSE Fieldn END
WHERE
    company_id = @company_id AND id = @id
gbn
  • 422,506
  • 82
  • 585
  • 676
  • @gbn: But with this method, each call to this sp should change only one field. This procedure updates the whole row. Is this efficient? – Naor Jan 26 '11 at 20:51
  • Can't remember the answer to that. Pretty sure its covered here [The Impact of Non-Updating Updates](http://sqlblog.com/blogs/paul_white/archive/2010/08/11/the_2D00_impact_2D00_of_2D00_update_2D00_statements_2D00_that_2D00_don_2D00_t_2D00_change_2D00_data.aspx) – Martin Smith Jan 26 '11 at 21:00
  • @Naor: Whether one field or all fields is trivial besides is updating one row out of many? AKA the WHERE clause could take longer. Also, if you had a trigger, all columns end up in INSERTED and DELETED anyway and you have to test to see what is updated anyway. And simply, you are updating anyway... does it matter? – gbn Jan 26 '11 at 21:06
  • 1
    @gbn: Just to throw out a potential hitch, it *would* affect the way updates are replicated if using SQL Server Replication Services and column-level merge replication. – Adam Robinson Jan 26 '11 at 22:07
  • @Adam Robinson: good point. I'm out of date with replication etc now – gbn Jan 26 '11 at 22:09
5

The important aspect to remember about SQL injection is that means that, if at all possible, you should never embed user-supplied values directly into your SQL. This doesn't mean that you can't use dynamic sql (though it definitely makes things easier if you don't), but it does become more dangerous at times.

In your specific example, you can keep the parameterization of everything except @field_name. This, unfortunately, must be embedded directly into the SQL; everything else can be passed as a parameter again, so there's no need to worry about their content.

The safest thing that you can do in this specific example is the following:

if(exists (select 1 from INFORMATION_SCHEMA.Columns where TABLE_NAME = 'Table' and TABLE_SCHEMA = 'dbo' and COLUMN_NAME = @fieldName))
begin
    DECLARE @sql nvarchar(1000)
    SET @sql = 'UPDATE dbo.TABLE '+
       'SET ' + QUOTENAME(@field_name) + '=@value ' + 
       'WHERE company_id=@company_id AND '+
       'id=@id'

    exec sp_executesql @sql,N'@id bigint, @company_id uniqueidentifier, @value nvarchar(50)',@id,@company_id,@value
end

This does two things:

  1. It verifies that there is actually a column with that name in the table. If the user were to embed any other SQL statements into the field, then this check would fail and the statement would not be executed. You could also call raiseerror to report the error, but I'll leave that exercise up to you.
  2. It encloses the field name in square braces so that field names that contain spaces or reserved words will not break the statement. This may not be an issue for you, but it's always good practice if you're generating the SQL yourself.
Martin Smith
  • 438,706
  • 87
  • 741
  • 845
Adam Robinson
  • 182,639
  • 35
  • 285
  • 343
  • Use `quotename` don't concatenate the square brackets yourself! Your code won't handle field names containing the `]` character correctly. – Martin Smith Jan 26 '11 at 20:56
  • @Martin: What if @value can be string or bigint? in my version it always worked. In your version it tells me: "Error converting data type nvarchar to bigint." because the field (in @field_name) is bigint. How do I automatically convert it? – Naor Feb 07 '11 at 02:03
  • @Adam Robinson: What if @value can be string or bigint? in my version it always worked. In your version it tells me: "Error converting data type nvarchar to bigint." because the field (in @field_name) is bigint. How do I automatically convert it? – Naor Feb 07 '11 at 02:10
  • @Naor - It is Adam's answer not mine! `@value` in your question would be a SQL injection avenue. maybe `sql_variant` type would work I haven't tested it. – Martin Smith Feb 07 '11 at 02:11
  • @Martin: You right - I am sorry. I didn't managed to do a thing with sql_variant. I also tried cast and convert according to data type in the INFORMATION_SCHEMA.Columns table but it doesn't secceed.. – Naor Feb 07 '11 at 02:32
0

I would start looking at ways to prevent sql injection attacks before you ever call the SP. Be careful with dynamic SQL strings pieced together from querystring or form data. Use a SqlCommand object.

Edit: In response to the comment, here is a nice explanation of how parameterized queries (SqlCommand queries) help prevent SQL injection.

From http://forums.asp.net/t/1568268.aspx:

...The placeholder - @Id - has become part of the hardcoded SQL. At runtime, the value provided by the querystring is passed to the database along with the hardcoded SQL, and the database will check the ProductID field as it attempts to bind the parameter value to it. This ensures a level of strong typing. If the parameter value is not the right type for the database field (a string, or numeric that's out of range for the field type), the database will be unable to convert it to the right type and will reject it. If the target field datatype is a string (char, nvarchar etc), the parameter value will be "stringified" automatically, which includes escaping single quotes. It will not form part of the SQL statement to be executed.

SquidScareMe
  • 3,108
  • 2
  • 24
  • 37
  • I am using SqlCommand in order to call the stored procedure. How can this help me prevent sql injection? – Naor Jan 26 '11 at 20:32
  • As I see things, the query itself should be in the DB and not in the code. – Naor Jan 26 '11 at 20:53
  • Well, I think the way SqLCommand works to help prevent SQL injection is the parameters to the stored procedure are strongly typed and cannot contain a query themselves. Not so with dynamic SQL. – SquidScareMe Jan 27 '11 at 12:23