SQL Server How to prevent sql injection in dynamic sql - sql-server

I have an ASP.Net MVC application which use SQL 2012 as the database server.
I have used Views,Stored Procedures (With/Without dynamic sql queries). I 've heard that dynamic sql can be a victim of sql injection.
Here is one of my sample dynamic query..
DECLARE #Username AS Varchar(100);
DECLARE #Password AS Varchar(100);
SET #Username = 'user1';
SET #Password = '123';
DECLARE #Query AS VARCHAR(MAX);
SET #Query = 'SELECT * FROM USERS WHERE Username ='+ #Username+ ' AND Password = '+#Password+';
EXEC(#Query)
How can I write this query preventing sql injection?

The premise is essentially the same in SQL as it is in application code... Never directly concatenate input as code but instead treat it as a parameter. So if your query is something like this:
SET #Query = 'SELECT * FROM USERS WHERE Username = #Username AND Password = #Password';
Then you can execute it with parameters using sp_executesql:
exec sp_executesql #Query, N'#Username varchar(100), #Password varchar(100)', #Username, #Password

Related

T-SQL SQL injection using JDBCConnector and StoredProcedures

I want to know if there is a possibility to inject sql query into procedure argument. I have particular case:
ALTER PROCEDURE [Test].[Injection]
#Query varchar(250) = null
AS
SET NOCOUNT ON
SET #Query = REPLACE(#Query,'','') COLLATE Latin1_General_CI_AI
... more sql code
SELECT * FROM Customers
WHERE (#Query IS NULL OR (Name COLLATE Latin1_General_CI_AI like '%'+#Query+'%'))
ORDER BY ExternalCode ASC
I want to inject sql query using #Query variable and possibly comment the rest of the code. Procedure is called via Web Service using JDBCConnector. I tried passing (and many others combinations):
'''abc'','''','''');SELECT * FROM [DummyTable][Dummy];--'
as #Query argument but it didn't work out.
No worries, SQL injection is impossible like this.
The way SQL injection works is by sneaking in (injecting) SQL code into the target query.
That is not possible to do with parameters, since SQL parameters are treated as data, not as code. You can pass any SQL code you want inside the parameter, but it will not pose an SQL injection threat.
However - please note that if you are using dynamic SQL inside your stored procedure, and concatenate the parameters into the SQL string, then your query is vulnerable to SQL injection attacks.
This code is not safe!
DECLARE #Sql nvarchar(max) = N'SELECT * FROM Customers
WHERE ('+ #Query +' IS NULL '....
EXEC(#SQL)
To safely run dynamic SQL in SQL Server you can use sp_executeSql and pass the parameters as parameters:
DECLARE #Sql nvarchar(max) = N'SELECT * FROM Customers
WHERE (#TheQuery IS NULL '....
EXEC sp_ExecuteSql
#Sql,
N'#TheQuery varchar(250)',
#TheQuery = #Query

Stored procedure to alter database recovery mode isnt compiling

I am trying to create a stored procedure that would be generic. I am trying to alter a database and set the recovery mode to either simple or full. It would accept database name and mode as parameter.
The SQL query executes in the context of the master database and alters the database specified. I am trying to incorporate it via Execute SQL task in SSIS. I need the stored procedure to reside in the database that is going to perform the operation on. Not sure how that is going to work. USE database keyword is not allowed in the stored procedure...
The original query works fine but I am facing an issue while trying to execute the stored procedure in the database.It says 'RECOVERY' is not a recognized SET option.
Original query:
use master
ALTER DATABASE XYZ
SET RECOVERY FULL
Stored procedure:
USE XYZ
GO
CREATE PROCEDURE DatabaseRecoveryMode
(#mode varchar(10),
#database varchar(50))
AS
BEGIN
ALTER DATABASE #database
SET RECOVERY #mode
END
The ALTER DATABASE documentation shows the recovery model is a keyword, not a variable. You'll need to construct and execute a dynamic SQL statement for this.
CREATE PROCEDURE dbo.DatabaseRecoveryMode
(
#mode nvarchar(11),
#database sysname
)
AS
IF #mode NOT IN(N'SIMPLE', N'BULK_LOGGED', N'FULL')
BEGIN
RAISERROR('Recovery model must be SIMPLE, BULK_LOGGED, OR FULL', 16, 1);
RETURN 1;
END;
DECLARE #SQL nvarchar(MAX) = N'ALTER DATABASE '
+ QUOTENAME(#database)
+ N' SET RECOVERY '+ #mode + N';';
EXECUTE(#SQL);
GO
You need to use dynamic SQL
USE XYZ
GO
Create Procedure DatabaseRecoveryMode
(
#mode varchar(10),
#database varchar(50)
)
AS
begin
DECLARE #SQL NVARCHAR(MAX)
DECLARE #db NVARCHAR(60), #Use NVARCHAR(100)
SET #db = N'master'
SET #Use = N'Use ' + #db
SET #SQL = #Use + N' ALTER DATABASE '+ #database + N' SET RECOVERY ' + #mode ;
--SELECT #SQL
EXEC sys.sp_executesql #SQL ;
end
GO

sp_addrolemember with WHERE clause - SQL Server

Is there a way to add a WHERE clause within the sp_addrolemember script so that I don't have to create the stored procedure in every single database?
For example in my stored procedure:
BEGIN
DECLARE #SQL NVARCHAR(MAX)
DECLARE #GrantSql INT
EXEC #GrantSql = sp_addrolemember 'db_owner', #LoginName WHERE DatabaseName = 'DBName'
IF #GrantSQL = 0
BEGIN
INSERT INTO TableName....
END
END
In documentation:
Adds a database user, database role, Windows login, or Windows group
to a database role in the CURRENT database
But you can try:
EXEC #GrantSql = DBName..sp_addrolemember 'db_owner', #LoginName
If DBName is a parameter, you should use parametrized dynamic sql
DECLARE #Sql NVARCHAR(MAX) =
N'EXEC #GrantSql = ' + QUOTENAME(#DBName) + '..sp_addrolemember #RoleName, #LoginName'
EXEC sp_executesql #Sql, N'#RoleName NVARCHAR(MAX), #LoginName NVARCHAR(255)', #RoleName, #LoginName
P.S.
Here is a good article about dynamic sql:
http://www.sommarskog.se/dynamic_sql.html
I think everyone who are about to write dynamic sql MUST read it

Script SQL Server login with Windows authentication without machine name

I want to write a SQL 2005 script to create a new login that uses Windows authentication. The Windows user is a local account (not a domain one). A local account with the same name exists on many SQL Server machines and I want to run the same script on all of them.
It seemed simple enough:
CREATE LOGIN [MyUser]
FROM WINDOWS
However, that doesn't work! SQL returns an error, saying Give the complete name: <domain\username>.
Of course, I can do that for one machine and it works, but the same script will not work on other machines.
Looks like sp_executesql is the answer, as beach posted. I'll post mine as well, because ##SERVERNAME doesn't work correctly if you use named SQL instances, as we do.
DECLARE #loginName SYSNAME
SET #loginName = CAST(SERVERPROPERTY('MachineName') AS SYSNAME) + '\MyUser'
IF NOT EXISTS (SELECT * FROM sys.server_principals WHERE [name] = #loginName)
BEGIN
DECLARE #sql NVARCHAR(1000)
SET #sql = 'CREATE LOGIN [' + #loginName + '] FROM WINDOWS'
EXEC sp_executesql #sql
END
This just worked for me:
DECLARE #sql nvarchar(1000)
SET #SQL = 'CREATE LOGIN [' + ##SERVERNAME + '\MyUser] FROM WINDOWS'
EXEC sp_executeSQL #SQL

How to pass parameter values to a T-SQL query

I am using the following T-SQL query in SQL server 2005 (Management Studio IDE):
DECLARE #id int;
DECLARE #countVal int;
DECLARE #sql nvarchar(max);
SET #id = 1000;
SET #sql = 'SELECT COUNT(*) FROM owner.myTable WHERE id = #id';
EXEC (#sql) AT oracleServer -- oracleServer is a lined server to Oracle
I am not sure how to pass the input parameter #id to the EXEC query, and pass the count result out to #countVal. I saw some examples for Microsoft SQL server like:
EXEC (#sql, #id = #id)
I tried this for Oracle but I got a statement error:
OLE DB provider "OraOLEDB.Oracle" for linked server "oracleServer"
returned message "ORA-00936: missing expression"
Try this:
EXEC sp_executesql #sql, N'#id int', #id
More info at this great article: http://www.sommarskog.se/dynamic_sql.html
As for the output, your SELECT needs to look something like this:
SELECT #countVal = COUNT(id) FROM owner.myTable WHERE id = #id
I'm selecting 'id' instead of '*' to avoid pulling unnecessary data...
Then your dynamic sql should be something like this:
EXEC sp_executesql #sql,
N'#id int, #countVal int OUTPUT',
#id,
#countVal OUTPUT
This example is adapted from the same article linked above, in the section sp_executesql.
As for your Oracle error, you will need to find out the exact SQL that sp_executesql is sending to Oracle. If there is a profiler or query log in Oracle, that may help. I have limited experience with Oracle, but that would be the next logical step for troubleshooting your problem.
The quick and dirty way is to just build the string before using the EXEC statement, however this is not the recommended practice as you may open yourself up to SQL Injection.
DECLARE #id int;
DECLARE #countVal int;
DECLARE #sql nvarchar(max);
SET #id = 1000;
SET #sql = 'SELECT COUNT(*) FROM owner.myTable WHERE id = ' + #id
EXEC (#sql) AT oracleServer -- oracleServer is a lined server to Oracle
The correct way to do this is to use the system stored procedure sp_executesql as detailed by magnifico, and recommended by Microsoft in Books Online is:
EXEC sp_executesql #sql, N'#id int', #id
I don't know why you are pass id separately.
You could do the following
SET #sql = 'SELECT COUNT(*) FROM owner.myTable WHERE id = ' + #id
Edit: Don't do the above. Use parameterized sql statements.

Resources