patternsqlModerate
How does use of sp_executesql with parameters protect against SQL injection?
Viewed 0 times
withsqlagainstsp_executesqldoeshowinjectionuseparametersprotect
Problem
The following is a dynamic filtering solution that uses sp_executesql
On p 541 of T-SQL Querying, it says
Because the dynamic code uses parameters rather than injecting the
constants into the code, it is not exposed to SQL injection attacks.
How does the use of parameters in sp_executesql protect against SQL injection?
Thank you
IF OBJECT_ID(N'dbo.GetOrders', N'P') IS NOT NULL DROP PROC dbo.GetOrders;
GO
CREATE PROC dbo.GetOrders
@orderid AS INT = NULL,
@custid AS INT = NULL,
@empid AS INT = NULL,
@orderdate AS DATE = NULL
AS
DECLARE @sql AS NVARCHAR(1000);
SET @sql =
N'SELECT orderid, custid, empid, orderdate, filler'
+ N' /* 27702431-107C-478C-8157-6DFCECC148DD */'
+ N' FROM dbo.Orders'
+ N' WHERE 1 = 1'
+ CASE WHEN @orderid IS NOT NULL THEN
N' AND orderid = @oid' ELSE N'' END
+ CASE WHEN @custid IS NOT NULL THEN
N' AND custid = @cid' ELSE N'' END
+ CASE WHEN @empid IS NOT NULL THEN
N' AND empid = @eid' ELSE N'' END
+ CASE WHEN @orderdate IS NOT NULL THEN
N' AND orderdate = @dt' ELSE N'' END;
EXEC sp_executesql
@stmt = @sql,
@params = N'@oid AS INT, @cid AS INT, @eid AS INT, @dt AS DATE',
@oid = @orderid,
@cid = @custid,
@eid = @empid,
@dt = @orderdate;
GOOn p 541 of T-SQL Querying, it says
Because the dynamic code uses parameters rather than injecting the
constants into the code, it is not exposed to SQL injection attacks.
How does the use of parameters in sp_executesql protect against SQL injection?
Thank you
Solution
double-up
To answer your question, you need to experiment with alternatives to using
Both of which can lead to SQL injection attacks, under the right circumstances.
It's probably worth noting that even totally unparameterized, the code above is relatively low-risk, since the data types being passed are not string types, but it's still possible.
Strings carry a much higher risk of a malicious payload.
The code examples below are from my presentation about using dynamic SQL in a different context, but apply pretty well to your question.
string-safe
You can use code like this safely, because the user input isn't part of the string that gets executed:
unsafe-strings
In this example, user input is concatenated into the string that gets executed, and isn't parameterized. This can cause problems:
The end result is a query that gets executed like so, which searches the
While many people will focus on memes like dropping tables, the real issue with dynamic SQL is usually theft of data. That's where the money is.
still-not-safe
Using
The same query as above will be executed.
back-to-safety
Using code that lines up better with your example, we can avoid SQL injection by assigning the value to a parameter instead of concatenating it directly into the string.
```
DECLARE
@SQLString nvarchar(MAX) = N'',
@Filter nvarchar(MAX) = N'',
@Title nvarchar(250) = N'''
UNION ALL
SELECT
t.object_id, t.schema_id, t.name, SCHEMA_NAME(t.schema_id), t.create_date, t.modify_date, NULL
FROM sys.tables AS t --';
/ This ends the current statement, and adds in some sneaky code /
SET @SQLString += N'
SELECT TOP (5000)
p.OwnerUserId, p.Score, p.Tags, p.Title, p.CreationDate, p.LastActivityDate, p.Body
FROM dbo.Posts AS p
WHERE p.OwnerUserId = 22656 ';
/ This appends the sneaky code onto our harmless query /
IF @Title IS NOT NULL
BEGIN
SET @Filter = @Filt
To answer your question, you need to experiment with alternatives to using
sp_executesql with parameters:- Using
EXEC(withoutsp_executesql)
- Using
sp_executesql(without parameters)
Both of which can lead to SQL injection attacks, under the right circumstances.
It's probably worth noting that even totally unparameterized, the code above is relatively low-risk, since the data types being passed are not string types, but it's still possible.
Strings carry a much higher risk of a malicious payload.
The code examples below are from my presentation about using dynamic SQL in a different context, but apply pretty well to your question.
string-safe
You can use code like this safely, because the user input isn't part of the string that gets executed:
DECLARE
@SQLString nvarchar(MAX) = N'',
@TableName sysname = N'Votes';
IF @TableName = N'Votes'
BEGIN
SET @SQLString += N'SELECT COUNT_BIG(*) AS records FROM dbo.Votes AS v;'
END
IF @TableName = N'Posts'
BEGIN
SET @SQLString += N'SELECT COUNT_BIG(*) AS records FROM dbo.Posts AS p;'
END
EXEC(@SQLString);
GOunsafe-strings
In this example, user input is concatenated into the string that gets executed, and isn't parameterized. This can cause problems:
DECLARE
@SQLString nvarchar(MAX) = N'',
@Filter nvarchar(MAX) = N'',
@Title nvarchar(250) = N'''
UNION ALL
SELECT
t.object_id, t.schema_id, t.name, SCHEMA_NAME(t.schema_id), t.create_date, t.modify_date, NULL
FROM sys.tables AS t --';
/* This ends the current statement, and adds in some sneaky code */
SET @SQLString += N'
SELECT TOP (5000)
p.OwnerUserId, p.Score, p.Tags, p.Title, p.CreationDate, p.LastActivityDate, p.Body
FROM dbo.Posts AS p
WHERE p.OwnerUserId = 22656 ';
/* This appends the sneaky code onto our harmless query */
IF @Title IS NOT NULL
BEGIN
SET @Filter = @Filter + N'
AND p.Title LIKE ''' + N'%' + @Title + N'%''';
END;
IF @Filter IS NOT NULL
BEGIN
SET @SQLString += @Filter;
END;
SET @SQLString += N'
ORDER BY p.Score DESC;';
/* Check the messages tab... */
RAISERROR('%s', 0, 1, @SQLString) WITH NOWAIT;
/* Check the results -- what's that at the end? */
EXEC (@SQLString);The end result is a query that gets executed like so, which searches the
Title column for a single wildcard, and then an additional result that lists all the tables in the database.SELECT TOP (5000)
p.OwnerUserId, p.Score, p.Tags, p.Title, p.CreationDate, p.LastActivityDate, p.Body
FROM dbo.Posts AS p
WHERE p.OwnerUserId = 22656
AND p.Title LIKE '%'
UNION ALL
SELECT
t.object_id, t.schema_id, t.name, SCHEMA_NAME(t.schema_id), t.create_date, t.modify_date, NULL
FROM sys.tables AS t --%'
ORDER BY p.Score DESC;While many people will focus on memes like dropping tables, the real issue with dynamic SQL is usually theft of data. That's where the money is.
still-not-safe
Using
sp_executesql is a good first step, but it still needs to be used with parameters. Code like below is still subject to SQL injection in the same way as above.DECLARE
@SQLString nvarchar(MAX) = N'',
@Filter nvarchar(MAX) = N'',
@Title nvarchar(250) = N'''
UNION ALL
SELECT
t.object_id, t.schema_id, t.name, SCHEMA_NAME(t.schema_id), t.create_date, t.modify_date, NULL
FROM sys.tables AS t --';
/* This ends the current statement, and adds in some sneaky code */
SET @SQLString += N'
SELECT TOP (5000)
p.OwnerUserId, p.Score, p.Tags, p.Title, p.CreationDate, p.LastActivityDate, p.Body
FROM dbo.Posts AS p
WHERE p.OwnerUserId = 22656 ';
/* This appends the sneaky code onto our harmless query */
IF @Title IS NOT NULL
BEGIN
SET @Filter = @Filter + N'
AND p.Title LIKE ''' + N'%' + @Title + N'%''';
END;
IF @Filter IS NOT NULL
BEGIN
SET @SQLString += @Filter;
END;
SET @SQLString += N'
ORDER BY p.Score DESC;';
/* Check the messages tab... */
RAISERROR('%s', 0, 1, @SQLString) WITH NOWAIT;
/* Check the results -- what's that at the end? */
EXEC sys.sp_executesql
@SQLString;The same query as above will be executed.
back-to-safety
Using code that lines up better with your example, we can avoid SQL injection by assigning the value to a parameter instead of concatenating it directly into the string.
```
DECLARE
@SQLString nvarchar(MAX) = N'',
@Filter nvarchar(MAX) = N'',
@Title nvarchar(250) = N'''
UNION ALL
SELECT
t.object_id, t.schema_id, t.name, SCHEMA_NAME(t.schema_id), t.create_date, t.modify_date, NULL
FROM sys.tables AS t --';
/ This ends the current statement, and adds in some sneaky code /
SET @SQLString += N'
SELECT TOP (5000)
p.OwnerUserId, p.Score, p.Tags, p.Title, p.CreationDate, p.LastActivityDate, p.Body
FROM dbo.Posts AS p
WHERE p.OwnerUserId = 22656 ';
/ This appends the sneaky code onto our harmless query /
IF @Title IS NOT NULL
BEGIN
SET @Filter = @Filt
Code Snippets
DECLARE
@SQLString nvarchar(MAX) = N'',
@TableName sysname = N'Votes';
IF @TableName = N'Votes'
BEGIN
SET @SQLString += N'SELECT COUNT_BIG(*) AS records FROM dbo.Votes AS v;'
END
IF @TableName = N'Posts'
BEGIN
SET @SQLString += N'SELECT COUNT_BIG(*) AS records FROM dbo.Posts AS p;'
END
EXEC(@SQLString);
GODECLARE
@SQLString nvarchar(MAX) = N'',
@Filter nvarchar(MAX) = N'',
@Title nvarchar(250) = N'''
UNION ALL
SELECT
t.object_id, t.schema_id, t.name, SCHEMA_NAME(t.schema_id), t.create_date, t.modify_date, NULL
FROM sys.tables AS t --';
/* This ends the current statement, and adds in some sneaky code */
SET @SQLString += N'
SELECT TOP (5000)
p.OwnerUserId, p.Score, p.Tags, p.Title, p.CreationDate, p.LastActivityDate, p.Body
FROM dbo.Posts AS p
WHERE p.OwnerUserId = 22656 ';
/* This appends the sneaky code onto our harmless query */
IF @Title IS NOT NULL
BEGIN
SET @Filter = @Filter + N'
AND p.Title LIKE ''' + N'%' + @Title + N'%''';
END;
IF @Filter IS NOT NULL
BEGIN
SET @SQLString += @Filter;
END;
SET @SQLString += N'
ORDER BY p.Score DESC;';
/* Check the messages tab... */
RAISERROR('%s', 0, 1, @SQLString) WITH NOWAIT;
/* Check the results -- what's that at the end? */
EXEC (@SQLString);SELECT TOP (5000)
p.OwnerUserId, p.Score, p.Tags, p.Title, p.CreationDate, p.LastActivityDate, p.Body
FROM dbo.Posts AS p
WHERE p.OwnerUserId = 22656
AND p.Title LIKE '%'
UNION ALL
SELECT
t.object_id, t.schema_id, t.name, SCHEMA_NAME(t.schema_id), t.create_date, t.modify_date, NULL
FROM sys.tables AS t --%'
ORDER BY p.Score DESC;DECLARE
@SQLString nvarchar(MAX) = N'',
@Filter nvarchar(MAX) = N'',
@Title nvarchar(250) = N'''
UNION ALL
SELECT
t.object_id, t.schema_id, t.name, SCHEMA_NAME(t.schema_id), t.create_date, t.modify_date, NULL
FROM sys.tables AS t --';
/* This ends the current statement, and adds in some sneaky code */
SET @SQLString += N'
SELECT TOP (5000)
p.OwnerUserId, p.Score, p.Tags, p.Title, p.CreationDate, p.LastActivityDate, p.Body
FROM dbo.Posts AS p
WHERE p.OwnerUserId = 22656 ';
/* This appends the sneaky code onto our harmless query */
IF @Title IS NOT NULL
BEGIN
SET @Filter = @Filter + N'
AND p.Title LIKE ''' + N'%' + @Title + N'%''';
END;
IF @Filter IS NOT NULL
BEGIN
SET @SQLString += @Filter;
END;
SET @SQLString += N'
ORDER BY p.Score DESC;';
/* Check the messages tab... */
RAISERROR('%s', 0, 1, @SQLString) WITH NOWAIT;
/* Check the results -- what's that at the end? */
EXEC sys.sp_executesql
@SQLString;DECLARE
@SQLString nvarchar(MAX) = N'',
@Filter nvarchar(MAX) = N'',
@Title nvarchar(250) = N'''
UNION ALL
SELECT
t.object_id, t.schema_id, t.name, SCHEMA_NAME(t.schema_id), t.create_date, t.modify_date, NULL
FROM sys.tables AS t --';
/* This ends the current statement, and adds in some sneaky code */
SET @SQLString += N'
SELECT TOP (5000)
p.OwnerUserId, p.Score, p.Tags, p.Title, p.CreationDate, p.LastActivityDate, p.Body
FROM dbo.Posts AS p
WHERE p.OwnerUserId = 22656 ';
/* This appends the sneaky code onto our harmless query */
IF @Title IS NOT NULL
BEGIN
SET @Filter = @Filter + N'
AND p.Title LIKE N''%'' + @Title + N''%'' ';
END;
IF @Filter IS NOT NULL
BEGIN
SET @SQLString += @Filter;
END;
SET @SQLString += N'
ORDER BY p.Score DESC;';
/* Check the messages tab... */
RAISERROR('%s', 0, 1, @SQLString) WITH NOWAIT;
/* Check the results -- what's that at the end now? */
EXEC sys.sp_executesql
@SQLString,
N'@Title NVARCHAR(250)',
@Title;Context
StackExchange Database Administrators Q#305558, answer score: 13
Revisions (0)
No revisions yet.