Single Quote Handling in Dynamic SQL Stored Procedure - sql

I'm using a query like this:
ALTER procedure [dbo].[procedure]
#Search nvarchar(100) = ''
declare #LargeComplexQuery nvarchar(max) =
'select * from People where Name like ''%' + #Search + '% order by Name'
exec sys.sp_executesql #LargeComplexQuery
#Search is populated by a program.
If the program returns a string containing a single quote the stored procedure errors, how can I handle this?
If possible, I'd like this to be handled by the stored procedure, rather than the program passing in the string.

Either escape the quote in the application before passing the parameter, or do it in the proc:
declare #Search nvarchar(100) = ''
declare #Search2 nvarchar(100)
declare #LargeComplexQuery nvarchar(max)
set #Search2 = replace(#Search, '''', '''''')
set #LargeComplexQuery = 'select * from People where Name like ''%' + #Search2 + '%''' -- I escaped the quote here too
exec sys.sp_executesql #LargeComplexQuery

You should escape the quotes after recovering the value.
And also COALESCE the parameter in case a NULL is passed to avoid the following error
EXEC sp_executesql NULL
Which would give
Procedure expects parameter '#statement' of type
'ntext/nchar/nvarchar'.
Which gives the following statements
DECLARE #Search nvarchar(100) = '';
SET #Search = REPLACE(COALESCE(#Search, ''), '''', '''''');
SET #LargeComplexQuery = 'SELECT * FROM People WHERE Name LIKE ''%' + #Search + '%'''
EXEC sys.sp_executesql #LargeComplexQuery

Here as a version that uses sp_executesql parameters and so is not vulnerable to SQL injection - it should also provide better performance, to quote MSDN:
Because the Transact-SQL statement itself remains constant and only
the parameter values change, the SQL Server query optimizer is likely
to reuse the execution plan it generates for the first execution.
CREATE PROCEDURE [dbo].[Yourproc]
(
#Search NVARCHAR(100) = N''
)
AS
DECLARE #LargeComplexQuery NVARCHAR(MAX) = 'SELECT * from People WHERE Name LIKE ''%'' + COALESCE(#Search, '''') + ''%'' ORDER BY Name'
EXEC sys.sp_executesql #LargeComplexQuery, N'#Search NVARCHAR(100)', #Search = #Search
I've made some assumptions, such as if you pass empty string or NULL as a search condition then you get all people returned.
Testing it out - dummy schema & data:
CREATE TABLE People(Name NVARCHAR(MAX))
INSERT INTO People(Name)
VALUES ('Mr Smith'), ('Mrs Jones'), ('Miss O'' Jones')
Testing stored proc execution:
DECLARE #search NVARCHAR(100) = N'Jones';
EXEC YourProc #Search; --Should get back both Mrs Jones and Miss O'Jones
SET #search = N'O'' Jones';
EXEC YourProc #Search; --Should get back just Miss O'Jones
SET #search = N'';
EXEC YourProc #Search; --Should get everyone, same as if you passed no search value at all
SET #search = NULL
EXEC YourProc #Search; --Should get everyone
MSDN Documentation on sp_executesql

Related

SQL Injection attack with stored procedures [duplicate]

This question already has answers here:
How to cleanse (prevent SQL injection) dynamic SQL in SQL Server?
(8 answers)
Closed 3 years ago.
I have a stored procedure as below:
CREATE PROCEDURE sp_GetName(#Name NVARCHAR(50))
AS
BEGIN
DECLARE #sqlcmd NVARCHAR(MAX);
DECLARE #params NVARCHAR(MAX);
SET #sqlcmd = N'SELECT * FROM [dbo].[Employee] WHERE Name like ''%' + #Name + '%''';
PRINT #sqlcmd;
SET #params = N'#Name NVARCHAR(50)';
EXECUTE sp_executesql #sqlcmd, #params, #Name;
END
EXEC sp_GetName '';WAITFOR DELAY '00:00:10'
Whenever I execute above statement, it always delays the response.
How can I write my procedure so that it will handle this SQL Injection attack.
Don't use dynamic SQL if you want to avoid SQL injection attacks. It doesn't matter whether you wrap the string concatenation code in a stored procedure or not.
In this case there's no reason to use dynamic SQL. You can simply write :
SELECT * FROM [dbo].[Employee] WHERE Name like '%' + #Name + '%'
to search for employees with a specific string in their name.
This query is immune to SQL injection whether you execute it as a parameterized query from a client or as a stored procedure. Clients would call both of them the same way.
The stored procedure should be just :
CREATE PROCEDURE getName(#Name nvarchar(50)
AS
SELECT * FROM [dbo].[Employee] WHERE Name like '%' + #Name + '%'
I removed the sp_ prefix because it's used for system stored procedures.
The problem with this code is that %something% will have to search the entire table. It can't use any index on Name. Only prefix searches can use indexes, ie #Name + '%' :
SELECT * FROM [dbo].[Employee] WHERE Name like #Name + '%'
You just need to parametrize the dynamic code.
CREATE PROCEDURE sp_GetName(#Name NVARCHAR(50))
AS
BEGIN
DECLARE #sqlcmd NVARCHAR(MAX);
DECLARE #params NVARCHAR(MAX);
SET #sqlcmd = N'SELECT * FROM [dbo].[Employee] WHERE Name like ''%'' + #Name + ''%''';
print #sqlcmd;
SET #params = N'#Name NVARCHAR(50)';
EXECUTE sp_executesql #sqlcmd, #params, #Name;
End
exec sp_GetName '';
However, the code has nothing that would justify the need of making it dynamic. It could be simply rewritten as this.
CREATE PROCEDURE sp_GetName(#Name NVARCHAR(50))
AS
SELECT * FROM [dbo].[Employee] WHERE Name like '%' + #Name + '%';
Why are you making your application slow by design?
You can just embed the SQL in you're stored procedure without using sp_executesql
CREATE PROCEDURE sp_GetName(#Name NVARCHAR(50))
AS
BEGIN
SELECT * FROM [dbo].[Employee] WHERE Name like '%' + #Name + '%'
End
Your example doesn't actually demonstrate a SQL injection attack; you've just got 2 sql statements seperated by a semi-colon, the second of which is a "wait"
-- Also demonstrates wait behaviour
exec sp_who;WAITFOR DELAY '00:00:10'

dynamic SQL update command

I am using MSSQL 2016,
I need to be able to update a row on a table dynamically.
I got a stored procedure :
CREATE PROCEDURE sp_lookupData_UpdatelookupValues
(
#FullTableName nvarchar(50),
#Id nvarchar(10),
#Name nvarchar(50),
#Description nvarchar(50)
)
AS
BEGIN
DECLARE #Cmd nvarchar(150) = N'UPDATE ' + #FullTableName + ' SET Name = ' + #Name + ', Description = ' + #Description + ' WHERE ID = ' + #Id + '';
EXECUTE sp_executesql #Cmd;
END
The problem is that Name and Description values are passed into the #Cmd like this :
UPDATE TABLE_NAME SET Name = Private, Description = Default WHERE ID = 1
Instead of 'Private' and 'Default'.
The result is an error where Private is being counted as a column which doesnt exist ( because of the bad format ).
Invalid column name 'Private'.
Put the quotes yourself
Use single quotes around Private and Default.
And since you are using dynamic querying, you have to double the single quotes to escape them.
DECLARE #Cmd nvarchar(150) = N'UPDATE ' + #FullTableName + ' SET Name = ''' + #Name + ''', Description = ''' + #Description + ''' WHERE ID = ' + #Id + '';
Also make sure you try the next solution, since the first one is SQL Injection compatible.
Use sp_executesql parameters
You can also use the parameters inside your #Cmd without doing the concatenation yourself but by passing the parameters to sp_executesql
Also I suggest you to QUOTENAME the #FullTableName parameter in case of spaces inside table's name.
DECLARE #Cmd nvarchar(150) = N'UPDATE QUOTENAME(#FullTableName) SET Name = #Name, Description = #Description WHERE ID = #Id;'
EXEC sp_executesql #Cmd, #FullTableName, #Name, #Description, #Id;
The advantage doing so, is you avoid any parameters not checked by the application to be able to do SQL Injection.
Reference :
https://learn.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-executesql-transact-sql
You have to add the quotes yourself. I prefer to use QUOTENAME to keep all the quotes recognizable:
QUOTENAME(#FullTableName, '''')
You cannot use parameters for identifiers. But, you can use parameters for values:
DECLARE #Cmd nvarchar(150) = N'
UPDATE ' + #FullTableName + '
SET Name = #Name,
Description = #Description
WHERE ID = #Id';
EXECUTE sp_executesql #Cmd,
N'#Name nvarchar(50), #Description nvarchar(50), #Id nvarchar(10)',
#Name = #Name, #Description = #Description, #Id = #id;
Unfortunately, the dynamic table name still poses risks, both in terms of SQL injection and syntax errors. I am guessing this is "controlled" code, not code with user input, so the risks might be acceptable. However, you probably should use quotename().
That brings up another issue which is probably the crux of the problem. Why do you have multiple tables with the same columns? Could these -- should these -- all be stored in a single table? This type of code calls into question aspects of the data model.

Pass the Multiple quotes around #variable wherever single quote is there

Below is my procedure. It is working fine.
Create PROCEDURE [dbo].[spCompanyName]
(
#CompanyName VARCHAR(100))
AS
Begin
DECLARE #sql VARCHAR(MAX)
SET #sql = ' Select EmpID,CompanyName FROM Employee' + CHAR(13) + CHAR(10)
IF len(#CompanyName) > 0
BEGIN
SET #sql = #sql + ' Where (RTRIM(LTRIM(CompanyName)) like ''' + #CompanyName + '%'') ' + char(13) + char(10)
END
PRINT #SQL
EXEC(#sql)
End
exec spCompanyName #CompanyName='So Unique, formerly Sofia''''s'
I need Wherever single quote is there in companyname,if I pass 8 quotes in single quotes I need output.above procedure where do I need to change.
Eg:
exec spCompanyName #CompanyName='So Unique, formerly Sofia''''''''s'
exec spCompanyName #CompanyName='Absolute''''''''s,Strategy''''''''s'
Don't think "I'll throw away all of the useful features of using parameters to separate data from code and start manually trying to protect strings" - keep using parameters.
Rather than
EXEC(#sql)
Have:
EXEC sp_executesql #sql,N'#CompanyName varchar(100)',#CompanyName = #CompanyName
And change:
SET #sql = #sql + ' Where (RTRIM(LTRIM(CompanyName)) like ''' + #CompanyName + '%'') ' + char(13) + char(10)
to:
SET #sql = #sql + ' Where (RTRIM(LTRIM(CompanyName)) like #CompanyName + ''%'') ' + char(13) + char(10)
Or, in the alternative, consider just writing a normal query, no dynamic SQL:
Create PROCEDURE [dbo].[spCompanyName]
(
#CompanyName VARCHAR(100))
WITH RECOMPILE
AS
Begin
Select EmpID,CompanyName FROM Employee
Where RTRIM(LTRIM(CompanyName)) like #CompanyName + '%' or
#CompanyName is null
End
Assuming you're using SQL Server 2008 (with particular patch levels) or later, as specified in Erland Sommarskog's Dynamic Search Conditions in T-SQL
If you are going to create a new stored procedure for this feature then you can have this in much better way then dynamic.
You should execute different select queries based on if CompanyName is passed or not.
You can simply the stored procedure as following.
Create PROCEDURE [dbo].[spCompanyName]
(
#CompanyName VARCHAR(100))
AS
Begin
IF LEN(#CompanyName) > 0
BEGIN
Select EmpID,CompanyName FROM Employee WHERE RTRIM(LTRIM(CompanyName)) like '' + #CompanyName + '%'
END
ELSE
BEGIN
Select EmpID,CompanyName FROM Employee
END
END
I am not sure why you need to pass so many single quotes in the parameter value but to me it looks like you can execute the procedure with following simple way.
EXEC spCompanyName #CompanyName='So Unique, formerly Sofia''s'
EXEC spCompanyName #CompanyName='Absolute''s,Strategy''s'
This should help you finding your solution.

Dynamic SQL in SQL Server

I have a SQL CLR stored procedure dbo.Parallel_AddSql that takes a SQL query to execute as a string parameter.
It looks something like this :
exec dbo.Parallel_AddSql 'sql1',
'insert into test.dbo.table_1 values (1, ''test1'')'
I need to pass my SQL query as dynamic SQL statement to Parallel_AddSql procedure.
SQL statement that I need to pass is procedure execution statement with parameters i.e
Exec dbo.MatchLastName #LastNameFromUser = #LastName,
#checkbool = #checkbool
How do I pass this? As #lastName and #checkbool will be out of scope if I pass them as such in the string.
I tried using this :
set #SQL = 'Exec dbo.MatchFirstName #FirstNameFromUser =' + #firstname + ',
#checkbool = ' + cast(#checkbool as nvarchar(10))
exec dbo.Parallel_AddSql 'sql1', #SQL
However, I get this error :
Could not find stored procedure 'dbo.MatchFirstName'
dbo.MatchFirstName is there but dbo.Parallel_AddSql is not able to see it at all.
dbo.Parallel_AddSql is coming the library code given here
Edit : Unclosed quotation mark error :
set #SQL = 'Exec dbo.MatchFirstName #FirstNameFromUser =''' + #firstname
+ ''', #checkbool = ''' + cast(#checkbool as nvarchar(10))
set #SQL = 'Exec dbo.MatchFirstName #FirstNameFromUser =''' + #firstname + ''', ...'
If you were to call the stored procure in a script, you'd write Exec dbo.MatchFirstName #FirstNameFromUser = 'John', not Exec dbo.MatchFirstName #FirstNameFromUser = John, right? Same thing with your dynamic SQL. You have to add in the quotes.
If you want to keep the #FirstName = #FirstName syntax, you'll have to declare and set your variables in the string itself. So,
'DECLARE #FirstName Varchar(Max); SET #FirstName = ''John''; EXEC ...'
EDIT
If you look at the example they gave for Parallel_AddSql, they used the fully qualified name of the procedure.
The example from www.codeproject.com:
exec ClrLibDb.dbo.Parallel_AddSql 'sql1', 'insert into test.dbo.table_1 values (1, ''test1'')'
I think it requires the database to be part of that. So, YourDbName.dbo.MatchFirstName. It may be looking at master for your stored procedure, which is why it's not finding it.

Invalid Column name error during execution of stored procedure

I am not able to execute the stored procedure. It is throwing an error
Invalid Column name 'DW201401'
Command used to execute the stored procedure:
exec RM_UTIL_MODE server,'DW201401'
Stored procedure code:
ALTER Procedure [dbo].[RM_UTIL_MODE]
#ServerName varchar(50),
#Value varchar(50)
As
Begin
declare #query nvarchar(max)
set #query = N'SELECT mode FROM ' + #ServerName +
N'.master.dbo.sysdatabases WHERE name =' + #Value
exec sp_executesql #query
End
But when I tried to run the query alone as shown below it is giving me result.
select mode, name
from server.master.dbo.sysdatabases
where name = 'DW201401'
Presumably, the issue is quotes around #Value:
declare #query nvarchar(max)
set #query = N'SELECT mode FROM '
+ #ServerName
+ N'.master.dbo.sysdatabases
WHERE name = '''+#Value+'''';
However, I would use parameter substitution instead:
declare #query nvarchar(max) ;
set #query = N'SELECT mode
FROM ' + #ServerName + N'.master.dbo.sysdatabases
WHERE name = #value';
exec sp_executesql #query, N'#value varchar(50)', #value = #value;
You are already using sp_executesql, so you might as well use it properly. Note: you cannot substitute the server name.
EDIT:
To elaborate on the comment, I would write the code this way:
declare #sql nvarchar(max) ;
set #sql = N'
SELECT mode
FROM #ServerName.master.dbo.sysdatabases
WHERE name = #value';
set #sql = replace(#sql, '#ServerName', quotename(#ServerName));
exec sp_executesql #sql, N'#value varchar(50)', #value = #value;
When using dynamic SQL, I no longer piece together the query using string concatenation. Instead, I put in place holders and use replace(). I find that concatenation is hard to maintain and often obscures what the SQL is doing. Although there is a bit more overhead in using replace() (and I often do it multiple times), it is worth it for preventing errors and maintaining the code (plus, my queries tend to run for a while anyway, so the overhead is minimal compared to the query time).
Your select looks like:
select mode, name from server.master.dbo.sysdatabases where name = DW201401
so you need to add escaped quotes in your dynamic query:
exec RM_UTIL_MODE cefmtqcfindv3,'DW201401'
ALTER Procedure [dbo].[RM_UTIL_MODE]
#ServerName varchar(50),#Value varchar(50)
As
Begin
declare #query nvarchar(max)
set #query = N'SELECT mode FROM '
+ #ServerName
+ N'.master.dbo.sysdatabases
WHERE name ='''+#Value+''''
exec sp_executesql #query
End
Just as a suggestion, when you are building a dynamic sql, try using PRINT instead of EXEC, then get what is printed and try it out. Most of the times you will know what went wrong.
Just as an example:
ALTER Procedure [dbo].[RM_UTIL_MODE]
#ServerName varchar(50),#Value varchar(50)
As
Begin
declare #query nvarchar(max)
set #query = N'SELECT mode FROM '
+ #ServerName
+ N'.master.dbo.sysdatabases
WHERE name ='''+#Value+''''
PRINT #query
--exec sp_executesql #query
End