I have an ASP.NET MVC 3 app that lets users to create their own filters. Something like amount > 5 and amount <= 7, and so on. The user can choose both the amount value and the operator.
My problem is how to pass those filters to the stored procedure that retrieves the data. The stored procedure is already pretty complicated, meaning there are a lot of parameters passed which are checked to be null, so I can't really apply the answer I found here: T-SQL Stored Procedure - Dynamic AND/OR Operator
Is there any other way I can do this?
Operators cannot be parameterized. Since you mention this is a stored procedure, the only option is to write the T-SQL inside the SP, and use sp_executesql, i.e.
//TODO : verify (whitelist) that #operator is in a known set of values...
// '=', '<>', '>', '<' etc - otherwise security risk
declare #sql varchar(4000) = 'select * from Foo where Bar '
+ #operator + ' #value'
exec sp_executesql #sql, N'#value int', #value
This builds the query (#sql) on the fly, but keeps the value (#value) parameterized throughout, so we only need to white-list the operator (#operator).
Just to show how the value remains parameterized, we could also have used:
//TODO : verify (whitelist) that #operator is in a known set of values...
// '=', '<>', '>', '<' etc - otherwise security risk
declare #sql varchar(4000) = 'select * from Foo where Bar '
+ #operator + ' #p'
exec sp_executesql #sql, N'#p int', #value
Here, #p is the name of the parameter in the inner sql, and #value is the name of the parameter in the stored procedure; the third/fourth/fifth/etc parameters to sp_executesql are mapped to the parameters declared in the second parameter to sp_executesql (which in this example, declared just #p).
Note that if this wasn't a stored procedure, you could perform the query-construction step in C#, again keeping the value as a parameter.
I think the only way is to use dynamic sql, e.g., sq_executesql. This allows you to create your SQL statement as a string, and then execute it. But it will probably mean a lot of rewriting of your existing stored procedure!
Related
I need to add the schema value dynamically to the query. I was trying to construct it the way we usually use values but realised it does not work with the schema names the same way.
This was what I was trying to do
sql = "SELECT Name FROM [#dbo].[Members]";
...
command.Parameters.Add("#dbo", SqlDbType.VarChar).Value = "dbo";
I know I can construct the query by directly adding the variable in the query like this:
sql = $#"SELECT Name FROM [{parameter}].[Members]";
But I wanted to disallow any kind of SQL Injection so want to go ahead using the parameterized query as above.
Can anyone help with a possible idea to implement this?
TIA
The schema is not a parameter. Query parameters are equivalent to function parameters in eg C#. They're used to pass values. In SQL, the table and columns are equivalent to C# types and properties. You can't specify them by name. The schema in a SQL query is similar to the Namespace in C#. The table is equivalent to a Type. In C#, just because Sales.Record and Diagnostics.Record have the same type name doesn't mean the two types can be used the same way.
The question doesn't explain why the schema name is passed dynamically. It's almost certain there are easier, more efficient and safer ways to query similar tables in multiple schemas, but the solution would depend on the actual problem.
There are some techniques that can be used to make such a dynamic query safe if not efficient. I'd really, really try to avoid treating the schema as a value though.
Using QUOTENAME
One option, is to use QUOTENAME in a T-SQL script to construct a dynamic query. At least this way a syntax error will be thrown if the schema and table names are wrong:
sql = #"declare #sql nvarchar(max)='SELECT Name FROM ' + QUOTENAME(#dbo) + '.[Members]';
select #sql;
exec sp_executesql #sql;";
...
command.Parameters.Add("#dbo", SqlDbType.NVarChar,100).Value = "dbo";
QUOTENAME will convert something like sys].schemas; PRINT ''x''; -- to [[sys]].schemas; PRINT 'x'; --]. This will result in an error :
declare #sql nvarchar(max)= 'select * from [' +quotename('sys].schemas; PRINT ''x''; --')
select #sql
exec sp_executesql #sql;
--------
select * from [[sys]].schemas; PRINT 'x'; --]
Invalid object name '[sys].schemas; PRINT 'x'; --'.
It's too easy to make quoting mistakes with such scripts. This could be extracted into a stored procedure :
CREATE PROCEDURE GetMemberNameBySchema
#dbo nvarchar(100)
as
declare #sql nvarchar(max)='SELECT Name FROM ' + QUOTENAME(#dbo) + '.[Members]';
exec sp_executesql #sql;
Verify the Schema name
Query sys.schema to ensue the schema is correct before constructing the query. Let's say you're using Dapper (so I don't have to write all the ADO.NET code) :
var schema="dbo";
var isValid=connection.ExecuteScalar<bool?>(
"select 1 from sys.schema where name=#name",
new {name=schema});
//isValid will be null if nothing is found
if(isValid ==true)
{
var names=connection.Query($"SELECT Name FROM [{schema}].[Members]");
...
}
This is safe to do because the first query ensured the schema name is valid.
You can only pass parameters to dynamic query not object names as far as I know. You can do something like below:
Update after seeing Panagiotis' solution. we can take out the exists part outside. I am no expert in C# but something similar we want to do:
DECLARE #Table NVARCHAR(100);
DECLARE #Schema NVARCHAR(50);
SET #Schema = 'dbo'; --This part will come from C#
SET #Table = 'Tablename'; --This part will come from C#
DECLARE #sql NVARCHAR(MAX);
IF EXISTS(SELECT * FROM sys.tables WHERE name = #Table and schema_id = SCHEMA_ID(#Schema))
BEGIN
SET #sql = N'SELECT TOP 1 * FROM '+ QUOTENAME(#Schema) + '.' + QUOTENAME(#Table) +';';
PRINT #sql;
EXEC sp_executesql #sql;
END
ELSE
RAISERROR('Table or Schema doesn''t exist.',16,1);
In the IF EXISTS you need to pass the table name and schema name as parameter to dynamic query.
In the actual query you need to make a concatenation of table name and schema name.
This will prevent sql injection as well and if the table is not there then raise an error.
In my project I have defined a stored procedure with a example code below:
CREATE PROCEDURE [dbo].[Stored]
#ParameterA AS varchar(128),
#ParameterB AS varchar(128),
#ParameterC AS varchar(400)
AS
BEGIN
DECLARE #query AS Varchar(MAX)
SET #query = 'SELECT *
FROM Table
WHERE A = '''+ #ParameterA + ''
IF #ParameterB = 'B'
BEGIN
SET #query = #query + ' AND C=''' + #ParameterC + ''
END
EXECUTE sp_executesql #query
END
I call this procedure with Entity Framework through the following code:
DBContext.Database.SqlQuery<Object>("Stored",
new SqlParameter("#p0", Param0),
new SqlParameter("#p1", Param1),
new SqlParameter("#p2", Param2)).ToList();
If I call a stored procedure with the string below, I generate a SQL injection:
Param2 = "ABC' ; DROP TABLE Table2"
How can I prevent this with Entity Framework?
You cannot
The underlying SQL procedure is faulty and a security nightmare. There is no way you can repair that on the layer on top of it. You are doing the best you can in EntityFramework, but it's still unsafe. You need to repair the problem (SQL proc) and not apply band aids to the layer using it.
sp_executesql seems to be a good starting point for a procedure that needs to have dynamic SQL and bind parameters.
you are creating a dynamic query, where you are concatenating parameters. this is causing issue.
do not use dynamic query, or validate parameters (if it contains any keywords or characters)
you can also rewrite your query into IF-ELSE structure on basis of parameters, so you do not need dynamic query.
Currently, I have a stored procedure that returns all the columns (Select *). I am using this stored procedure to retrieve data from multiple tables, each table with different number of columns and obviously different column names, so Select * is convenient. However, now I need to retrieve only specific columns from each table so I want to pass the parameters something like this:
SELECT #param1, #param2, #param3, etc.
FROM #tableName
WHERE columnName = #tableId
The problem here is the number of parameters to be passed for the columns isn't set, as the table can have any number of columns. Is there a way to use some kind of loop or dynamic assignment so that I can pass any number of parameters as column names?
I know that I can filter out only the columns I want to use, and just leave out the rest, but that doesn't work in my case. I need the stored procedure to NOT return some specific columns with sensitive data.
I am using SQL Server 2008, ASP.NET MVC 4, and C# in my application.
If you are able to modify your stored procedure, you can easily put the required columns definitions as a parameter and use an auto-created temporary table:
CREATE PROCEDURE sp_GetDiffDataExample
#columnsStatement NVARCHAR(MAX) -- required columns statement (e.g. "field1, field2")
AS
BEGIN
DECLARE #query NVARCHAR(MAX)
SET #query = N'SELECT ' + #columnsStatement + N' INTO ##TempTable FROM dbo.TestTable'
EXEC sp_executeSql #query
SELECT * FROM ##TempTable
DROP TABLE ##TempTable
END
In this case you don't need to create a temp table manually - it is created automatically.
Hope this helps.
You can just pass one parameter that is a comma-delimited string of the columns you want to select and build a dynamic sql string.
#sql = 'SELECT ' + #param + ' FROM MyTable...';
EXECUTE (#sql);
If you use a dynamic sql solution, you should take care to guard against sql injection attacks.
You might also consider continuing to get all columns from the stored procedure, and showing only the columns the user wants in the front end.
Although I really do not like the stored procedure approach for this problem (I agree with #Gusman that building the query in C# is a better approach) you can make a stored procedure work without opening yourself to SQL Injection attacks. The following example is one simple way to do this:
Let's say that the table in question has the columns named COL1, COL2, and COL3. The stored procedure would accept a varchar(max) parameter named #IncludeCols and have code like:
SELECT CASE WHEN #IncludeCols LIKE '%#COL1#%' THEN COL1 ELSE '' END AS COL1,
CASE WHEN #IncludeCols LIKE '%#COL2#%' THEN COL2 ELSE '' END AS COL2,
CASE WHEN #IncludeCols LIKE '%#COL3#%' THEN COL3 ELSE '' END AS COL3
FROM <Table name>
WHERE <Where clause>
Yes, a column of every name will be returned, but data will only come from columns whose names are in the parameter. For example, if you wanted COL1 and COL3, the parameter value would be #COL1#COL3#. The # is important and must be on each side of every column name or any LIKE clause could get a false positive match.
If this is to export data to Excel then the best approach is likely to have a stored procedure that returns all of your columns minus the ones that should never be exported (passwords, other protected data, timestamps perhaps) and then filter out any additional unwanted columns in your front end.
You should never be using SELECT * for several reasons. Define what your application needs and program to that.
I've got a stored procedure that returns data for a grid control. Given a table name, the grid will display data from that table. The user can sort and filter this data. There is also paging logic for large data sets.
The names of the tables that data is pulled from is not known until runtime, so dynamic SQL was used. This works well, but is vulnerable to SQL injection - the tableName, sortExpression and filterExpression variables are generated clientside and passed through to the server.
Below is a simplified version of the procedure:
create procedure ReadTable (
#tableName as varchar(128),
#sortExpression as varchar(128),
#filterExpression as varchar(512)
)
as
begin
declare #SQLString as nvarchar(max) =
'select * from ' + #tableName +
' where ' + #filterExpression +
' order by ' + #sortExpression
exec Sp_executesql #SQLString
end
I'm struggling to find a way to easily prevent SQL injection in this case. I've found a good answer explaining how to check the #tableName is legitamite (How should I pass a table name into a stored proc?), but the approach won't work for the filtering or sort strings.
One way would be perhaps to do some sanitizing server side before the data is passed through to the database - breaking the expressions down into column names and checking them against the known column names of the table.
Would there be an easier way?
Im using such a query in my stored procedure
SET #Statement =
'SELECT Id,Title,Content,Status,ROW_NUMBER()
OVER (ORDER BY ' + #Sort + ') AS StudentReport
FROM YearBook
WHERE ' + #Criteria + ')
AS ArticleNumber
WHERE StudentReport> ' + CONVERT(NVARCHAR, #StartRowIndex) + ' AND
StudentReport<= (' + CONVERT(NVARCHAR, #StartRowIndex + #MaximumRows);
Just want to know whether its possible to do sql injection to this stored procedure. If yes, how can i prevent it? Need Help !!!
Yes it's possible. Quite easy, even. Try setting
#Criteria = "\r\nGO\r\nexec sp_addlogin 'hacker', 'broken'\r\nGO";
The batch will product errors, but the part in between will run nevertheless so welcome your new login.
The correct way to do your query could be something like this.
CREATE PROC FindSomething
#StartRowIndex int,
#MaximumRows int,
#Sort int, -- 1-4 representing the columns, say in a dropdown
#Id int,
#Content varchar(max),
#Title varchar(max),
#Status int
AS
SELECT Id,Title,Content,Status
FROM
(
SELECT Id,Title,Content,Status,
ROW_NUMBER() OVER (ORDER BY
CASE when #Sort = 1 then Id
when #Sort = 4 then Status
end,
CASE when #sort = 2 then Title
when #sort = 3 then Content
end) AS StudentReport
FROM YearBook
WHERE (#id is null or #id = Id)
AND (#Content is null or #Content = Content)
AND (#Title is null or #Title = Title)
AND (#status is null or #Status = Status)
) Numbered
WHERE StudentReport >= #StartRowIndex
AND StudentReport <= #StartRowIndex + #MaximumRows
OPTION (RECOMPILE);
GO
Read here on more about dynamic searching: www.sommarskog.se/dyn-search.html
Note: I split up 1/4 and 2/3 in the sort because each branch of a CASE statement must produce the same type, or that is compatible. int/varchar is very bad mix to have in a case statement.
Assuming the above is a string that you are building and then executing with EXEC or sp_executesql then Yes, SQL injection is possible.
How to prevent it depends on what you are trying to do. Perhaps you need to rethink your approach.
Yes, it will. You can still do things to help defend against it tho'
For example, #Sort is a column name, so you can escape that properly (and ensure that if someone tried to inject something into is it won't work, because it has been properly escaped. For that use QUOTENAME.
QUOTENAME(#Sort)
#Criteria is more difficult because you are actually expecting a fragment on SQL code so it becomes very difficult to work out what is valid and what is malicious. You might want to reconsider what you are trying do do here. If you must use Criteria then ensure that the security model is set up so that only the application(s) that absolutely needs it has access to the stored proc that does this. Make sure that the validation in the application before it sends of the SQL to ensure that anything it is doing isn't going to be damaging.
It looks like you are trying to make a pretty generic search stored procedure with paging. These are difficult to implement properly in t-sql only, and can become maintenance headaches down the road due to the branching logic, or additional supporting stored procedures you need to add...
I would start to look at other options outside of a pure sql approach. Using an orm, or micro orm could help a lot. Actually, take a look at what Sam Saffron came up with...
http://samsaffron.com/archive/2011/09/05/Digging+ourselves+out+of+the+mess+Linq-2-SQL+created