Is sql injection possible in stored procedures with text param? - c#

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

Related

How to pass schema name in a parameterized query

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.

Preventing SQL injection when using dynamic SQL

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?

How to prevent SQL injection using a stored procedure while insert record?

I am new to SQL Server, I am trying to insert records into table using a stored procedure as shown below.
I want a suggestion that is using the below stored procedure. Also:
can I prevent SQL injection?
is it the right way?
Correct me if I miss anything in below procedure which leads to SQL injection.
Create PROCEDURE [dbo].[spInsertParamTable]
#CmpyCode nvarchar(50),
#Code nvarchar(50),
#DisplayCode nvarchar(50),
#TotalDigit int,
#Nos bigint,
#IdentitY int OUTPUT
AS
BEGIN
INSERT tblParamTable (CmpyCode, Code, DisplayCode, TotalDigit, Nos)
VALUES (#CmpyCode, #Code, #DisplayCode, #TotalDigit, #Nos)
END
SELECT #Identity = SCOPE_IDENTITY();
RETURN #Identity
SQL Injection specifically refers to injecting SQL code into an existing SQL query that's built up via string concatenation and executed dynamically. It is almost always of the form:
#dynamicSQL = "select * from sensitivetable where field = " + #injectableParameter
sp_executesql #dynamicSQL
For this particular stored procedure, the worst an attacker could do is insert unhelpful values into your tblParamTable.
However, if these values are then used in a dynamically-built query later on, then this merely becomes a second-order attack: insert values on page 1, see results of dynamic query on page 2. (I only mention this since your table is named tblParamTable, suggesting it might contain parameters for later re-use.)
Can I prevent SQL injection?
You already are - there is no way to "inject" code into your SQL statement since you're using parameters.
Is it the right way?
Well, there's not one "right" way - but I don't see anything seriously wrong with what you're doing. A few suggestions:
You don't need to RETURN your output parameter value. Setting it is enough.
You have the last SELECT outside of the BEGIN/END block, which isn't hurting anything but for consistency you should put everything inside BEGIN/END (or leave them out altogether).

passing an operand as an sql parameter

I am currently working on an asp.net application that has sql server 2008 as its backend. I want to give the user the ability to specify what they want to filter by on the SQL statement.
On the interface I am giving them the option to select the following as a dropdown:
equals to
greater than
Less than
etc
I want to pass this as a parameter on the sql query to be executed. How best can I achieve this?
for eg;
Select amount, deduction, month from loan where amount #operant 10000;
the #operand is the return values of the above dropdown which is = < > <= >=
Assuming all positive integers < 2 billion, this solution avoids multiple queries and dynamic SQL. OPTION (RECOMPILE) helps thwart parameter sniffing, but this may not be necessary depending on the size of the table, your parameterization settings and your "optimize for ad hoc workload" setting.
WHERE [Amount] BETWEEN
CASE WHEN #operand LIKE '<%' THEN 0
WHEN #operand = '>' THEN #operant + 1
ELSE #operant END
AND
CASE WHEN #operand LIKE '>%' THEN 2147483647
WHEN #operand = '<' THEN #operant - 1
ELSE #operant END
OPTION (RECOMPILE);
I would write few "IF" statements. Code is not very short, but should be fast.
IF(#operand = '=')
Select..
ELSE IF(#operand = '>=')
Select..
...
Also, i would say, that Top (#someRowCount) could be great idea.
You need dynamic sql for this scenario
For your example this can be
DECLARE #sql AS nvarchar(max) -- Use max if you can, if you set
-- this to a specific size then your assignment later can be
-- truncated when maintained and still be valid.
SET #sql = 'Select amount, deduction, month from dbo.loan where amount '
+ #operand + ' 10000'
EXEC sp_executesql #sql
Update 1
There are 2 ways to execute dynamic sql : Exec() and sp_executesql
Read the comments why sp_executesql is preferred (still, beware of sql injections!)
I also prefix the table with the dbo so that the execution plan can be cached between different users
More info in the awesome paper at http://www.sommarskog.se/dynamic_sql.html#queryplans

Wish to observe stored procedures that exist on multiple database and across multiple servers

Having an issue on MS SQL 2005 Enterprise multiple servers where I want to get a collection of meta data across multiple servers and multiple databases. I saw on Stack Overflow a good example on using the magical sp_MSforeachdb that I altered a little bit below. Basically a MS stored procedure is being ran dynamically and it's looking for anytime a database (?) is like a name like 'Case(fourspaces)'. This is great and it gives me what I want but for only a single server. I want to do this for more, is it possible SQL gurus?
Example thus far:
SET NOCOUNT ON
DECLARE #AllTables table (CompleteTableName varchar(256))
INSERT INTO #AllTables (CompleteTableName)
EXEC sp_msforeachdb 'select distinct ##SERVERNAME+''.''+ ''?'' + ''.'' + p.name from [?].sys.procedures p (nolock) where ''?'' like ''Case____'''
SELECT * FROM #AllTables ORDER BY 1
Is there a way though to do this in SQL, Linq, or ADO.NET to perform this clever built in stored procedure that inserts into a table variable to do this multiple times across servers BUT...... Put that in one set. As far as I know you CANNOT switch servers in a single session in SQL Management Studio but I would love to be proved wrong on that one.
EG: I have a production environment with 8 Servers, each of those servers has many databases. I could run this multiple times but I was hoping that if the servers were linked already I could do this from the sys views somehow. However I am on an environment using SQL 2005 and got MS's download for the sys views and it looks like the sys.servers is on an island unto itself where the SERVERID does not seem to join to anything else.
I would be willing to use an ADO.NET reader or LINQ in a C# environment and possibly call the above TSQL code multiple times but ...... Is there a more efficient way to get the info directly in TSQL IF the servers are LINKED SERVERS? Just curious.
The overall purpose of this operation is for deployment purposes to see how many procedures exist across all servers and databases. Now we do have SQL compare from Redgate but I am unaware if it can script procs that don't exist to exist the same as set A. Even if it could I would like to try to make something on my own if feasible.
Any help is much appreciated and if you need further clarification please ask.
I figured it out, once you set up linked servers you can merely extend the linked server name to the left of the object to qualify it more distinctly.
EG instead of sp_msforeachdb I can do (Servername).MASTER..sp_msforeachdb. I can then iterate through my servers if they are LINKED(they are in my case) from the sys.servers table.
I did some things that would slow things down with my left join and that I store everything at once and then examine with a 'like' statement instead of an explicit qualifier. But overall I think this solution will provide an end user with flexibility to not know the exact name of an object to hunt for. I also like that I can now use this with SSIS, SSRS and ADO.NET as the procedure can do the hunting iteration for me and I do not have to do something in an apps memory but on the SQL server's. I'm sure others may have better ideas but I did not hear anything so this is mine:
Complete solution below:
Create PROC [PE].[DeployChecker]
(
#DB VARCHAR(128)
, #Proc VARCHAR(128)
)
AS
BEGIN
--declare variable for dynamic SQL
DECLARE
#SQL VARCHAR(512)
, #x int
-- remove temp table if it exists as it should not be prepopulated.
IF object_ID('tempdb..#Procs') IS NOT NULL
DROP TABLE tempdb..#Procs
-- Create temp table to catch built in sql stored procedure
CREATE TABLE #Procs --DECLARE #Procs table
(
ServerName varchar(64)
, DatabaseName VARCHAR(128)
, ObjectName VARCHAR(256)
)
SET #X = 1
-- Loops through the linked servers with matching criteria to examine how MANY there are. Do a while loop while they exist.
-- in our case the different servers are merely incrementing numbers so I merely do a while loop, you could be more explicit if needed.
WHILE #X <= (SELECT count(*) FROM sys.servers WHERE name LIKE 'PCTRSQL_')
BEGIN
-- for some reason I can't automate the 'sp_msforeachdb' proc to take dynamic sql but I can set a variable to do it and then run it.
SET #SQL = 'Insert Into #Procs Exec PCTRSQL' + CAST(#X AS VARCHAR(2)) + '.MASTER..sp_msforeachdb ' +
'''select ##SERVERNAME, ''''?'''', name from [?].sys.procedures (nolock) where ''''?'''' like ''''%' + #DB + '%'''' '''
Exec (#SQL)
SET #X = #X + 1
END
;
-- Find distinct Server detail
WITH s AS
(
SELECT Distinct
ServerName
, DatabaseName
FROM #Procs
)
-- do logic search in the select statement to see if there is a proc like what is searched for
, p AS
(
SELECT
ServerName
, DatabaseName
, CASE WHEN ObjectName LIKE '%' + #Proc + '%' THEN ObjectName END AS ProcName
FROM #Procs
where ObjectName LIKE '%' + #Proc + '%'
)
-- now do a left join from the distinct server cte to the lookup for the proc cte, we want to examine ALL the procs that match a critera
-- however if nothing eixsts we wish to show a NULL value of a single row for a reference to the Servername and Database
SELECT
s.ServerName
, s.DatabaseName
, p.ProcName
, CAST(CASE WHEN ProcName IS NOT NULL THEN 1 ELSE 0 END AS bit) AS ExistsInDB
FROM s
LEFT JOIN p ON s.ServerName = p.ServerName
AND s.DatabaseName = p.DatabaseName
ORDER BY DatabaseName, ServerName, ProcName
END

Categories