Reputation: 63
I wrote the following stored procedure for querying the database. Can someone tell me if this dynamic query stored procedure is vulnerable to a SQL injection attack?
If it is, how to modify the following code to prevent SQL injection attacks?
The second question is OPTION (RECOMPILE)
at the end of the WHERE
cause, is it necessary with every execution?
CREATE PROCEDURE DataMapMainQuery
(@DataMapID VARCHAR(MAX),
@DataMapIDName VARCHAR(MAX),
@StartIndex INT,
@MaximumRows INT,
@sortExpression VARCHAR(MAX))
AS
BEGIN
DECLARE @FilteredTotalRows AS INT
DECLARE @SqlString NVARCHAR(MAX)
DECLARE @WhereString1 NVARCHAR(MAX)
DECLARE @WhereString2 NVARCHAR(MAX)
IF (@DataMapID IS NULL)
SET @WhereString1 = ' AND (DataMapID LIKE ' + '''%%''' + ' OR NULL IS NULL)'
ELSE
SET @WhereString1 = ' AND (DataMapID LIKE ' + '''%' + @DataMapID + '%''' + ' OR ''' + @DataMapID + ''' IS NULL)'
IF (@DataMapIDName IS NULL)
SET @WhereString2 = ' AND (DataMapIDName LIKE ' + '''%%''' + ' OR NULL IS NULL)'
ELSE
SET @WhereString2 = ' AND (DataMapIDName LIKE ' + '''%' + @DataMapIDName + '%''' + ' OR ''' + @DataMapIDName + ''' IS NULL)'
IF (@sortExpression IS NULL)
SET @sortExpression = 'DataMapID'
SELECT
@FilteredTotalRows = COUNT(*)
FROM
DataMapMain
WHERE
1 = 1
AND (DataMapID LIKE '%' + @DataMapID + '%' OR @DataMapID IS NULL)
AND (DataMapIDName LIKE '%' + @DataMapIDName + '%' OR @DataMapIDName IS NULL)
IF (@FilteredTotalRows < @StartIndex + 1)
BEGIN
SET @SqlString = '
SELECT
DataMapID, DataMapIDName,
DataMapGroup, DataMapGroupRemark,
CONVERT(BIGINT, TimeStamp) AS TimeStamp
FROM
(SELECT
ROW_NUMBER() OVER (ORDER BY ' + @sortExpression + ') AS RowNumber,
DataMapID, DataMapIDName,
DataMapGroup, DataMapGroupRemark,
TimeStamp
FROM
DataMapMain
WHERE
1 = 1'
+ @WhereString1
+ @WhereString2
+ ') DataMapMain
WHERE
RowNumber >= 1
AND RowNumber < (1 + ' + CONVERT(NVARCHAR(10), @MaximumRows) + ')
OPTION (RECOMPILE)'
END
ELSE
BEGIN
SET @SqlString = '
SELECT
DataMapID
,DataMapIDName
,DataMapGroup
,DataMapGroupRemark
,CONVERT(bigint, TimeStamp) as TimeStamp
FROM
(
Select ROW_NUMBER() over (order by ' + @sortExpression + ') as RowNumber
,DataMapID
,DataMapIDName
,DataMapGroup
,DataMapGroupRemark
,TimeStamp
From DataMapMain
WHERE
1 = 1'
+ @WhereString1
+ @WhereString2
+ ') DataMapMain
WHERE
RowNumber >= (' + CONVERT(nvarchar(10),@StartIndex) + ' + 1) and RowNumber < (' + CONVERT(nvarchar(10),@StartIndex) + ' + 1 + ' + CONVERT(nvarchar(10),@MaximumRows) + ' )
OPTION (RECOMPILE)'
END
PRINT @SqlString
PRINT @FilteredTotalRows
EXEC sp_executesql @SqlString
END
Upvotes: 2
Views: 609
Reputation: 63
Thanks for all of your helps, I rewrote the code below. Please let me know if it is not OK. thank you all!
CREATE PROCEDURE DataMapMainQuery
(@DataMapID VARCHAR(MAX),
@DataMapIDName VARCHAR(MAX),
@StartIndex INT,
@MaximumRows INT,
@sortExpression VARCHAR(MAX))
AS
BEGIN
DECLARE @FilteredTotalRows AS INT
DECLARE @SqlString NVARCHAR(MAX)
DECLARE @params NVARCHAR(MAX);
DECLARE @WhereString1 NVARCHAR(MAX)
DECLARE @WhereString2 NVARCHAR(MAX)
IF (@DataMapID IS NULL)
SET @WhereString1 = ' AND (DataMapID LIKE ' + '''%%''' + ' OR NULL IS NULL)'
ELSE
SET @WhereString1 = ' AND (DataMapID LIKE ' + '''%' + @DataMapID + '%''' + ' OR ''' + @DataMapID + ''' IS NULL)'
IF (@DataMapIDName IS NULL)
SET @WhereString2 = ' AND (DataMapIDName LIKE ' + '''%%''' + ' OR NULL IS NULL)'
ELSE
SET @WhereString2 = ' AND (DataMapIDName LIKE ' + '''%' + @DataMapIDName + '%''' + ' OR ''' + @DataMapIDName + ''' IS NULL)'
IF (@sortExpression IS NULL)
SET @sortExpression = 'DataMapID'
SELECT
@FilteredTotalRows = COUNT(*)
FROM
DataMapMain
WHERE
1 = 1
AND (DataMapID LIKE '%' + @DataMapID + '%' OR @DataMapID IS NULL)
AND (DataMapIDName LIKE '%' + @DataMapIDName + '%' OR @DataMapIDName IS NULL)
IF (@FilteredTotalRows < @StartIndex + 1)
BEGIN
SET @SqlString = '
SELECT
DataMapID, DataMapIDName,
DataMapGroup, DataMapGroupRemark,
CONVERT(BIGINT, TimeStamp) AS TimeStamp
FROM
(SELECT
ROW_NUMBER() OVER (ORDER BY ' + @sortExpression + ') AS RowNumber,
DataMapID, DataMapIDName,
DataMapGroup, DataMapGroupRemark,
TimeStamp
FROM
DataMapMain
WHERE
1 = 1'
+ @WhereString1
+ @WhereString2
+ ') DataMapMain
WHERE
RowNumber >= 1
AND RowNumber < (1 + ' + CONVERT(NVARCHAR(10), @MaximumRows) + ')'
END
ELSE
BEGIN
SET @SqlString = '
SELECT
DataMapID
,DataMapIDName
,DataMapGroup
,DataMapGroupRemark
,CONVERT(bigint, TimeStamp) as TimeStamp
FROM
(
Select ROW_NUMBER() over (order by ' + @sortExpression + ') as RowNumber
,DataMapID
,DataMapIDName
,DataMapGroup
,DataMapGroupRemark
,TimeStamp
From DataMapMain
WHERE
1 = 1'
+ @WhereString1
+ @WhereString2
+ ') DataMapMain
WHERE
RowNumber >= (' + CONVERT(nvarchar(10),@StartIndex) + ' + 1) and RowNumber < (' + CONVERT(nvarchar(10),@StartIndex) + ' + 1 + ' + CONVERT(nvarchar(10),@MaximumRows) + ' )'
END
SET @params = '
@DataMapID VARCHAR(MAX)
,@DataMapIDName VARCHAR(MAX)
,@StartIndex INT
,@MaximumRows INT
,@sortExpression VARCHAR(MAX)';
EXEC sp_executesql
@SqlString
,@params
,@DataMapID
,@DataMapIDName
,@StartIndex
,@MaximumRows
,@sortExpression;
END
Upvotes: 0
Reputation: 1724
Adding OPTION(RECOMPILE) hint provides to rebuild a new execution plan for the query execution for every execution. Under some circumstances it can help to improve the performance. However the recompile operation uses memory and CPU resources in order to generate new execution plan. As a result, if you are not sure about the effects of the performance you don't use it
Upvotes: 0
Reputation: 43636
Just use sp_executesql
with parameters. Build your dynamic T-SQL statements, but instead the value add @parameter_name
. Then call the routine like this:
EXEC sp_executesql @sql
,N'@parameter_name1 INT, @parameter_name2 VARCHAR(128), @parameter_name3 BIT'
,@parameter_name1, @parameter_name2, @parameter_name3;
Upvotes: 1
Reputation: 13006
So far your @DataMapID
and @DataMapName
are safe because your building it first before applying in your main sql query. I would suggest adding these lines to check proper values of your sort expression, maxrows and start index
IF (@sortExpression NOT IN ('ASC', 'DESC'))
BEGIN
RAISERROR('invalid order expression', 16,1);
RETURN;
END;
IF (TRY_CAST(@StartIndex as int) = null or TRY_CAST(@MaximumRows as int) = null)
BEGIN
RAISERROR('invalid startindex or maximum rows', 16,1);
RETURN;
END;
Upvotes: 0