I have a stored procedure in SQL Server which gets a XML as input parameter. In this XML is defined - what stored procedure with which parameters values should be executed. And according to that the stored procedure execute the wanted one using dynamic SQL with sp_executesql.
The problem is that the values of the parameters are vulnerable to SQL injection.
I have tried using typed parameters like that:
EXEC sys.sp_executesql
#stmt = #sql,
#params = N'#Username SYSNAME, #HireDate DATE',
#UserName = #Username, #HireDate = #HireDate;
But it doesn't really work in my case, because I don't know what procedure with what parameters will be executed. The number of parameters may vary and some of them are optional / have default values etc. All I can get is the names of the parameters as string :(
After some parsing of the input XML the SQL Query is build and executed like that
declare #params nvarchar(max);
select #params = coalesce(#params + N', ', N' ') + r.attrName + N' = ' + iif(p.isNumericType = 1, r.Value, '''' + r.Value /*cast(r.Value as nvarchar(max))*/ + '''') --+ r.Value
from dbo.#ruleConfig r
left join #spParams p on p.paramName = r.attrName -- datatype of a parameter from information_schema.parameters
declare #sql nvarchar(max) = (select #procName + isnull(#params, N''));
exec dbo.sp_executesql #sql
The value of #sql can look something like that:
'core.GetUser #LogonName = 'myDomain\myLogon''
But also can look like that:
'core.GetUser #fullLogonName = 'myDomain\myLogon;'WAITFOR DELAY '0:0:20';--'' and that's the problem.
To begin with, if you have any functionality that requires you to send dynamic SQL stored procedures execution commands, you have a big problem with your design as with a strict tightly-coupled design, each API call will be mapped to a single stored procedure.
If you still wish to stick with the current design, than you have to create a list of known stored procedures and their values. You cannot just accept the fact that you "don't know what procedure with what parameters will be executed" because than your publish an SQL Injection functionality by-design.
You need to create an enum of the known stored procedures (for example 1 = proc1, 2 = proc2 etc.) and perform a regular-expression-based input validation to their parameters.
In the example you just gave, if you want "myDomain\myLogon" to be accepted and "myDomain\myLogon;'WAITFOR DELAY '0:0:20';--" to be rejected, you can for example use a regular expression that looks like this:
^([A-Za-z\\])*$
You can test it in the regexr website.
You'll have to create an input validation regex for each field type - for example names, usernames, emails etc. Basically it's pretty similar to just creating a separate API for each procedure call, with proper input validation.
Related
Due to the constraints within the workplace I have to use a local stored procedure to call another remote stored proc on a linked sql server, however the problem lies in passing a necessary parameter to the remote stored proc.
This is the query I constructed:
select *
from OPENQUERY([REMOTE_SRVR],'exec db.dbo.dwStoredProc_sp ''#id''')
In order to pass #id to the remote stored proc I understand I could concatenate the above as a string and then use exec
Something along the lines of:
set #query = 'select * from OPENQUERY([REMOTE_SRVR], ''EXEC db.dbo.dwStoredProc_sp '' #id '''''
exec(#query)
I cannot get the local stored proc to successfully call the other. The single quote mess doesn't help!
I get the error: Could not find stored procedure 's'
To help with the quote mess I like to do this in steps. It is more code but easier to understand. I am not sure from your example if #id is an integer. In that case you can lose the double quotes around __ID__.
set #query = 'EXEC db.dbo.dwStoredProc_sp ''__ID__'''
set #query = REPLACE(#query,'__ID__',#id)
set #query = REPLACE(#query,'''','''''')
set #query = REPLACE('SELECT * FROM OPENQUERY([REMOTE_SRVR], ''__REMOTEQUERY__'')','__REMOTEQUERY__',#query)
You could avoid dynamic queries by simply by using EXEC (..., ParamValue) AT LinkedServer (see product's documentation, example [L. Using a parameter with EXECUTE and AT linked_server_name]):
1) On target server:
CREATE PROCEDURE dbo.Proc1( #id NVARCHAR(50) )
AS
SELECT #id AS [id];
GO
2) On the source server you create the linked server and then you can call the stored procedure using EXEC ... AT ... syntax:
DECLARE #p1 NVARCHAR(50);
SET #p1 = N'DROP TABLE dbo.CocoJambo'
EXECUTE (N'dbo.Proc1 ? ' , #p1 ) AT LOCALINKEDSEREV
Output:
id
------------------------
DROP TABLE dbo.CocoJambo
I was wondering if I can make a stored procedure that insert values into dynamic table.
I tried
create procedure asd
(#table varchar(10), #id int)
as
begin
insert into #table values (#id)
end
also defining #table as table var
Thanks for your help!
This might work for you.
CREATE PROCEDURE asd
(#table nvarchar(10), #id int)
AS
BEGIN
DECLARE #sql nvarchar(max)
SET #sql = 'INSERT INTO ' + #table + ' (id) VALUES (' + CAST(#id AS nvarchar(max)) + ')'
EXEC sp_executesql #sql
END
See more here: http://msdn.microsoft.com/de-de/library/ms188001.aspx
Yes, to implement this directly, you need dynamic SQL, as others have suggested. However, I would also agree with the comment by #Tomalak that attempts at universality of this kind might result in less secure or less efficient (or both) code.
If you feel that you must have this level of dynamicity, you could try the following approach, which, although requiring more effort than plain dynamic SQL, is almost the same as the latter but without the just mentioned drawbacks.
The idea is first to create all the necessary insert procedures, one for every table in which you want to insert this many values of this kind (i.e., as per your example, exactly one int value). It is crucial to name those procedures uniformly, for instance using this template: TablenameInsert where Tablename is the target table's name.
Next, create this universal insert procedure of yours as follows:
CREATE PROCEDURE InsertIntValue (
#TableName sysname,
#Value int
)
AS
BEGIN
DECLARE #SPName sysname;
SET #SPName = #TableName + 'Insert';
EXECUTE #SPName #Value;
END;
As can be seen from the manual, when invoking a module with the EXECUTE command, you can specify a variable instead of the actual module name. The variable in this case should be of a string type and is supposed to contain the name of the module to execute. This is not dynamic SQL, because the syntax is not the same. (For this to be dynamic SQL, the variable would need to be enclosed in brackets.) Instead, this is essentially parametrising of the module name, probably the only kind of natively supported name parametrisation in (Transact-)SQL.
Like I said, this requires more effort than dynamic SQL, because you still have to create all the many stored procedures that this universal SP should be able to invoke. Nevertheless, as a result, you get code that is both secure (the #SPName variable is viewed by the server only as a name, not as an arbitrary snippet of SQL) and efficient (the actual stored procedure being invoked already exists, i.e. it is already compiled and has a query plan).
You'll need to use Dynamic SQL.
To create Dynamic SQL, you need to build up the query as a string. Using IF statements and other logic to add your variables, etc.
Declare a text variable and use this to concatenate together your desired SQL.
You can then execute this code using the EXEC command
Example:
DECLARE #SQL VARCHAR(100)
DECLARE #TableOne VARCHAR(20) = 'TableOne'
DECLARE #TableTwo VARCHAR(20) = 'TableTwo'
DECLARE #SomeInt INT
SET #SQL = 'INSERT INTO '
IF (#SomeInt = 1)
SET #SQL = #SQL + #TableOne
IF (#SomeInt = 2)
SET #SQL = #SQL + #TableTwo
SET #SQL = #SQL + ' VALUES....etc'
EXEC (#SQL)
However, something you should really watch out for when using this method is a security problem called SQL Injection.
You can read up on that here.
One way to guard against SQL injection is to validate against it in your code before passing the variables to SQL-Server.
An alternative way (or probably best used in conjecture) is instead of using the EXEC command, use a built-in stored procedure called sp_executesql.
Details can be found here and usage description is here.
You'll have to build your SQL slightly differently and pass your parameters to the stored procedure as arguments as well as the #SQL.
I'm wondering if there is a way within a stored procedure in SQL Server 2008 to require that named parameters be used? Failing that, a way to tell if named parameters were used would suffice.
We're looking at developing a few relatively complex stored procedures that will be called by external systems, and I'm looking for ways to minimize the risk that the external system has data mixed up and/or missing.
The stored procedures will deal with a relatively large number of parameters (for us at least), up to a few dozen. Thanks!
When writing the stored procedures, include MUST PASS variables at the top and optional ones next by assigning default values.
Also you can validate them inside the stored procedure Eg:
If #Var = ''
Begin
SET #Var = 'Default'
END
Another thing you can use is exceptions in .Net. When you did not pass a variable that is required by the stored proc, it will throw an exception saying parameter x is required but not supplied (or something similar to this). Catch that exception and send it back to the user in an appropriate way.
Or Validate all the parameters at the application level (Even before getting them into stored procedure)
This sproc uses the "dbcc inputbuffer" command. To use this command you need "VIEW SERVER STATE" permission or belong to the sysadmin role. I suppose you can enforce the named parameter requirement by raising an error when no parameter names are used.
create procedure usp_Example
#LastName nvarchar(50),
#FirstName nvarchar(50),
#Initials nvarchar(10) = 'Default Initials'
as begin
declare #inputBuffer table (eventtype nvarchar(30), parameters int, eventinfo nvarchar(255))
declare #execStatement nvarchar(255)
--get sql for sproc execution
insert into #inputBuffer
exec ('dbcc inputbuffer (' + ##spid + ') with no_infomsgs')
select top 1 #execStatement = eventinfo from #inputBuffer
--get named parameters
select s.name
from sys.all_parameters s where s.object_id = ##PROCID and CHARINDEX(s.name,#execStatement) > 0
--do stored proc specific operations
end
go
--test with no parameter naming
exec usp_Example 'LastName','FirstName','Initials'
go
--test with parameter naming
exec usp_Example #lastName = 'LastName',#Firstname = 'FirstName',#Initials = 'Initials'
go
--test with parameter naming and default value
exec usp_Example #LastName = 'LastName',#FirstName = 'FirstName'
go
I am attempting to make a stored procedure that uses sp_executesql. I have looked long and hard here, but I cannot see what I am doing incorrectly in my code. I'm new to stored procedures/sql server functions in general so I'm guessing I'm missing something simple. The stored procedure alter happens fine, but when I try run it I'm getting an error.
The error says.
Msg 1087, Level 15, State 2, Line 3
Must declare the table variable "#atableName"
The procedure looks like this.
set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
go
ALTER PROCEDURE [dbo].[sp_TEST]
#tableName varchar(50),
#tableIDField varchar(50),
#tableValueField varchar(50)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQLString nvarchar(500);
SET #SQLString = N'SELECT DISTINCT #aTableIDField FROM #atableName';
EXEC sp_executesql #SQLString,
N'#atableName varchar(50),
#atableIDField varchar(50),
#atableValueField varchar(50)',
#atableName = #tableName,
#atableIDField = #tableIDField,
#atableValueField = #tableValueField;
END
And I'm trying to call it with something like this.
EXECUTE sp_TEST 'PERSON', 'PERSON.ID', 'PERSON.VALUE'
This example isn't adding anything special, but I have a large number of views that have similar code. If I could get this stored procedure working I could get a lot of repeated code shrunk down considerably.
Thanks for your help.
Edit: I am attempting to do this for easier maintainability purposes. I have multiple views that basically have the same exact sql except the table name is different. Data is brought to the SQL server instance for reporting purposes. When I have a table containing multiple rows per person id, each containing a value, I often need them in a single cell for the users.
You can not parameterise a table name, so it will fail with #atableName
You need to concatenate the first bit with atableName, which kind defeats the purpose fo using sp_executesql
This would work but is not advisable unless you are just trying to learn and experiment.
ALTER PROCEDURE [dbo].[sp_TEST]
#tableName varchar(50),
#tableIDField varchar(50),
#tableValueField varchar(50)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQLString nvarchar(500);
SET #SQLString = N'SELECT DISTINCT ' + quotename(#TableIDField) + ' FROM ' + quotename(#tableName);
EXEC sp_executesql #SQLString;
END
Read The Curse and Blessings of Dynamic SQL
You cannot use variables to pass table names and column names to a dynamic query as parameters. Had that been possible, we wouldn't actually have used dynamic queries for that!
Instead you should use the variables to construct the dynamic query. Like this:
SET #SQLString = N'SELECT DISTINCT ' + QUOTENAME(#TableIDField) +
' FROM ' + QUOTENAME(#TableName);
Parameters are used to pass values, typically for use in filter conditions.
Is there any chance to create temporary stored procedure or function on MS SQL 2005? I would like to use this stored procedure only in my query so after execution it will be gone.
I have a query I would like to EXEC against some data. But for every table I will process this command, I need to change some parts of it. So I thought I would create temporary SP that would return for me a query from arguments I provide (like table name and so on) and than execute this query by EXEC.
And this stored procedure will be not useful for me later so I would like to have it temporary so that when I end executing my query - it will disappear.
This question is a bit old, but the other answers failed to provide the syntax for creating temporary procedures. The syntax is the same as for temporary tables: #name for local temporary objects, ##name for global temporary objects.
CREATE PROCEDURE #uspMyTempProcedure AS
BEGIN
print 'This is a temporary procedure'
END
This is described in the "Procedure Name" section of the official documentation. http://technet.microsoft.com/en-us/library/ms187926%28v=sql.90%29.aspx
I'm using this technique to deduplicate the code for my primitive T-SQL unit tests. A real unit testing framework would be better, but this is better than nothing and "garbage collects" after itself.
Re your edit - it sounds like you should be using sp_ExecuteSQL against a (parameterized) nvarchar that contains TSQL.
Search on sp_ExecuteSQL; a simple example:
DECLARE #SQL nvarchar(4000),
#Table varchar(20) = 'ORDERS',
#IDColumn varchar(20) = 'OrderID',
#ID int = 10248
SET #SQL = 'SELECT * FROM [' + #Table + '] WHERE ['
+ #IDColumn + '] = #Key'
EXEC sp_executesql #SQL, N'#Key int', #ID
Note that table and column names must be concatenated into the query, but values (such as #Key) can be parameterized.
There is a temporary stored procedure - but it is per connection, not per sp.
However, you might want to look at Common Table Expressions - they may be what you are after (although you can only read from them once).
Maybe if you can clarify what you are trying to do?
Just use the SQL of the stored proc inside your query. No need to create a stored procedure inside the DB, it won't give you any advantage over a normal query inside your query.