Securing Dynamic SQL from SQL Injection - sql

I have a dynamic script running on all of the objects in a database and change the schema name for every one from [dbo] to the database name.
The script is working just fine, I would like to know if I can do anything better in order to secure it from SQL Injection?
BEGIN TRANSACTION
/* Change schema to all objects in database (from dbo)*/
DECLARE #SchemaName SYSNAME = db_name();
DECLARE #SQL NVARCHAR(MAX) = N'IF Not Exists (select 1 from sys.schemas where schema_id = SCHEMA_ID(#NewSchemaName))
EXEC(''CREATE SCHEMA ''+#NewSchemaName+'''')' + NCHAR(13) + NCHAR(10);
SELECT #SQL = #SQL + N'EXEC(''ALTER SCHEMA ''+#NewSchemaName+'' TRANSFER [' + SysSchemas.Name + '].[' + DbObjects.Name + ']'');' + NCHAR(13) + NCHAR(10)
FROM sys.Objects DbObjects
INNER JOIN sys.Schemas SysSchemas
ON DbObjects.schema_id = SysSchemas.schema_id
WHERE SysSchemas.Name = 'dbo'
AND (DbObjects.Type IN ('U', 'P', 'V'))
EXECUTE sp_executesql #sql, N'#NewSchemaName sysname', #NewSchemaName = #SchemaName
ROLLBACK
In my quest of securing this one, I used this great article by Thom Andrews:
Dos and Don'ts of Dynamic SQL
this is where I started: github.com/NathanLifshes

The script below should be much more secure.
Note the use of the QUOTENAME function in the beginning of the script.
This would work because if you use the QUOTENAME function "inline" inside an EXEC command, you may get a syntax error. So you need to apply it at an earlier stage.
As luck would have it, you have such an "earlier" stage when you initialize the #SchemaName variable:
BEGIN TRANSACTION
/* Change schema to all objects in database (from dbo)*/
DECLARE #SchemaName SYSNAME = QUOTENAME(db_name());
DECLARE #SQL NVARCHAR(MAX) = N'IF Not Exists (select 1 from sys.schemas where schema_id = SCHEMA_ID(#NewSchemaName))
EXEC(''CREATE SCHEMA ''+#NewSchemaName+'''')' + NCHAR(13) + NCHAR(10);
SELECT #SQL = #SQL + N'EXEC(''ALTER SCHEMA ''+#NewSchemaName+'' TRANSFER ' + QUOTENAME(SysSchemas.Name) + '.' + QUOTENAME(DbObjects.Name) + ''');' + NCHAR(13) + NCHAR(10)
FROM sys.Objects DbObjects
INNER JOIN sys.Schemas SysSchemas
ON DbObjects.schema_id = SysSchemas.schema_id
WHERE SysSchemas.Name = 'dbo'
AND (DbObjects.Type IN ('U', 'P', 'V'))
PRINT #SQL
EXECUTE sp_executesql #sql, N'#NewSchemaName sysname', #NewSchemaName = #SchemaName
ROLLBACK

In this case, I don't see an actual risk for SQL injection, since no value is supplied by a user. The script takes only the database name as an input. The only option to utilize SQL injection is by injecting a command into a database name. This is possible, of course. In order to protect against this option, you should use the QUOTENAME function to properly quote the schema name within your dynamic script.

Related

Invalid object name when iterating over all tables

I am trying to iterate over all the tables with a given schema name and make a copy in the same db with another given schema.
This is the script I am using:
use DoctorWho
declare #sql_query as nvarchar(max)
select #sql_query = concat('insert into doctor_generated.' , table_name , ' select * from ' , table_name , ';')
FROM INFORMATION_SCHEMA.tables
WHERE table_schema LIKE 'dbo%';
exec (#sql_query);
However this throws an error:
Invalid object name 'doctor_generated.tblEpisodeEnemy
Upon searching this error, I've refreshed the local cache & made sure I am using the correct db.
Is there anything I am missing?
I suspect what you actually want is something like this. Firstly use string aggregation for your dynamic statement; I assume you are on a fully supported version of SQL Server as you don't state you aren't. Next use QUOTENAME to properly quote your objects and avoid injection.
Then you can execute your dynamic statement:
DECLARE #SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
SELECT #SQL = STRING_AGG(N'SELECT * INTO doctor_generated.' + QUOTENAME(t.name) + N' FROM ' + QUOTENAME(s.name) + N'.' + QUOTENAME(t.name) + N';',#CRLF)
FROM sys.schemas s
JOIN sys.tables t ON s.schema_id = t.schema_id
WHERE s.[name] = N'dbo';
--PRINT #SQL;
EXEC sys.sp_executesql #SQL;

Stored procedure to copy table using dynamic query in SQL Server

I want to create a stored procedure for coping table using dynamic query.
I followed this step for creating stored proceduce, link:-https://stackoverflow.com/questions/8698231/sql-server-stored-procedures-to-copy-tables
but I got an error:
Could not find stored procedure 'Select * into tblDetail from salesDetail'
Here is my code:
ALTER PROCEDURE sp_copyOneTableToAnother
#newtable nvarchar(50),
#oldtable nvarchar(50)
AS
BEGIN
DECLARE #sql nvarchar(1000)
SET #sql = 'SELECT * INTO ' + #newtable +
' FROM ' + #oldtable
EXEC #sql
END
exec sp_copyOneTableToAnother #newtable='tblDetail',#oldtable='salesDetail'
The stored procedure was created from above syntax but while calling sp_copyOneTableToAnother, I get an error. Please help me solve it.
There are several problems here, first, your procedure name starts with sp_, which is reserved by Microsoft for Special / System Procedures. That should go.
Next, your parameter types are wrong; the correct data type for an object is a sysname, a synonym of nvarchar(128) NOT NULL, not varchar.
Next, the injection issue; you blindly inject the values of your parameters into your statement and hope that said values aren't malicious. Validate the value of #oldtable and properly quote both parameters.
Finally, the execution should be done by sp_executesql; not using it promotes further injection issues as you can't parametrise EXEC (#SQL) statements.
You also don't define your schemas, which you really should be. I add them as NULLable parameters here, and get the USER's default schema
This results in something like this:
CREATE OR ALTER PROCEDURE dbo.CopyOneTableToAnother #NewTable sysname,
#OldTable sysname,
#NewSchema sysname = NULL,
#OldSchema sysname = NULL AS
BEGIN
SET NOCOUNT ON;
SELECT #NewSchema = ISNULL(#NewSchema,default_schema_name),
#OldSchema = ISNULL(#OldSchema,default_schema_name)
FROM sys.database_principals
WHERE name = USER_NAME();
DECLARE #SQL nvarchar(MAX);
SELECT #SQL = N'SELECT * INTO ' + QUOTENAME(#NewSchema) + N'.' + QUOTENAME(#NewTable) + N' FROM ' + QUOTENAME(s.[name]) + N'.' + QUOTENAME(t.[name]) + N';'
FROM sys.schemas s
JOIN sys.tables t ON s.schema_id = t.schema_id
WHERE s.name = #OldSchema
AND t.[name] = #OldTable;
EXEC sys.sp_executesql #SQL;
END;
db<>fiddle

Iterate thru on database tables and delete some of them based on the name of the table

Hello I would like to loop thru my database tables and delete the ones that I don't need. Also I would like this code to be a stored procedure.
I would like to iterate thru on this select's table_name_to_be_deleted:
SELECT name as table_name_to_be_deleted
FROM sys.tables
WHERE 7=7
and name like 'x_%'
and modify_date< dateadd(day,-10,GETDATE())
And drop every table that I have in the table_name_to_be_deleted column
drop table *variable*
Sorry no minimum viable product as I am not that familiar in T-SQL, but I would much appreciate your help!
You can use Dynamic SQL to do this. Making use of the sys.schemas and sys.tables you could do something like this:
CREATE PROC dbo.DeleteArchives #OlderThan date AS
BEGIN
DECLARE #SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
SET #SQL = STUFF((SELECT #CRLF +
N'DROP TABLE ' + QUOTENAME(s.[name]) + N'.' + QUOTENAME(t.[name])
FROM sys.schemas s
JOIN sys.tables t ON s.schema_id = t.schema_id
WHERE t.[name] LIKE N'x[_]%'
AND t.modify_date < #OlderThan
FOR XML PATH (N''),TYPE).value('.','nvarchar(MAX)'),1,2,N'');
EXEC sys.sp_executesql #SQL;
END;

Execute a set of stored procedures

I am trying to create a stored procedure to execute all the stored procedures in a schema. This is what I have:
ALTER procedure [ALL_IC].[EXECUTE_ICS]
#sql nvarchar(max) = null,
#fa nvarchar(max) = null
as
begin
set #sql = ('select (select ''EXEC [IC].['' + b.name + ''];''
from sys.procedures b
join sys.schemas s on s.schema_id = b.schema_id
where s.name = ''IC''
for xml path(''''))'
) end
EXECUTE sp_executesql #sql
Running this selects the string to execute all the stored procedures, but it doesn't actually execute them.
EXEC ALL_IC.EXECUTE_ICS
How can I actually execute all the stored procedures by running that line of code?
SQL Server 2012
Aside from the numerous awful things this brings to mind the actual logic is quite simple. You just need to build a dynamic string and execute it.
Here is a very simple way you can do this.
declare #SQL nvarchar(max) = ''
select #SQL = #SQL + 'EXEC ' + quotename(s.name) + '.' + quotename(b.name) + ';'
from sys.procedures b
join sys.schemas s on s.schema_id = b.schema_id
where s.name = 'IC'
select #SQL
--exec sp_executesql #SQL
--EDIT--
Changed slightly so the schema is not hard coded inside the dynamic sql. This way if you want a different schema you just change the schema name and everything else will still work.
--Second Edit--
Changed to use QuoteName instead of hardcoding in the []. This is more flexible and stable.
Just to make sure you are not executing any procedures that expect a parameter, I added a little check against the system catalogue sys.parameters. The rest is pretty much the same what you were trying to do in your question.
Declare #sql NVARCHAR(MAX);
SELECT #sql = STUFF((SELECT ' Exec ' + QUOTENAME(s.name) +'.'+ QUOTENAME(p.name) + '; '
FROM sys.procedures P
INNER JOIN sys.schemas S ON P.schema_id = S.schema_id
WHERE s.name = 'IC'
AND NOT EXISTS (SELECT 1
FROM sys.parameters pm
WHERE p.object_id = pm.object_id)
FOR XML PATH('')),1,2,'');
Exec sp_executesql #sql

Object does not exist or is not a valid using 'sp_changeobjectowner'

I changed the table schema from dbo to db_owner using the SQL statement below (SQL 2008 R2):
DECLARE #old sysname, #new sysname, #sql varchar(1000)
SELECT
#old = 'db_owner'
, #new = 'dbo'
, #sql = '
IF EXISTS (SELECT NULL FROM INFORMATION_SCHEMA.TABLES
WHERE
QUOTENAME(TABLE_SCHEMA)+''.''+QUOTENAME(TABLE_NAME) = ''?''
AND TABLE_SCHEMA = ''' + #old + '''
)
EXECUTE sp_changeobjectowner ''?'', ''' + #new + ''''
EXECUTE sp_MSforeachtable #sql
I need to change it back by switch the old and new name parameters, but I am getting an error:
Msg 15001, Level 16, State 1, Procedure sp_changeobjectowner, Line 75
Object '[db_owner].[language_link]' does not exist or is not a valid
object for this operation.
That table does exist though and even with that old db_owner. Any way to fix this?
Here is a screenshot on how I could tell it is still owned by db_owner. Only some tables were moved back properly:
Are you sure you should be using sp_changeobjectowner? (Objects don't really have owners anymore as of SQL 2005.) How did you verify that db_owner.language_link exists? Personally I would use ALTER SCHEMA for this and I would also lean toward catalog views (sys.tables) rather than information_schema.tables. Finally, I wouldn't use the undocumented and unsupported sp_MSforeachtable - I have highlighted issues with sp_MSforeachdb that are likely potential issues here because the code is quite similar.
DECLARE
#old SYSNAME = N'db_owner',
#new SYSNAME = N'dbo',
#sql NVARCHAR(MAX) = N'';
SELECT #sql += CHAR(13) + CHAR(10) + 'ALTER SCHEMA ' + #new
+ ' TRANSFER ' + QUOTENAME(SCHEMA_NAME([schema_id]))
+ '.' + QUOTENAME(name) + ';'
FROM sys.tables AS t
WHERE SCHEMA_NAME([schema_id]) = #old
AND NOT EXISTS (SELECT 1 FROM sys.tables WHERE name = t.name
AND SCHEMA_NAME([schema_id]) = #new);
PRINT #sql;
--EXEC sp_executesql #sql;
EDIT adding code to find objects that are common to both schemas. And to move the ones already in the new schema to some dummy schema:
CREATE SCHEMA dummy AUTHORIZATION dbo;
GO
DECLARE
#old SYSNAME = N'db_owner',
#new SYSNAME = N'dbo',
#sql NVARCHAR(MAX) = N'';
SELECT #sql += CHAR(13) + CHAR(10) + 'ALTER SCHEMA dummy TRANSFER '
+ QUOTENAME(#new) + '.' + QUOTENAME(t1.name) + ';'
FROM sys.tables AS t1
INNER JOIN sys.tables AS t2
ON t1.name = t2.name
WHERE t1.schema_id = SCHEMA_ID(#new)
AND t2.schema_id = SCHEMA_ID(#old);
PRINT #sql;
-- EXEC sp_executesql #sql;
But really it just sounds like you messed something up and it requires some manual cleanup...
EDIT adding evidence because OP seems convinced that this code is not working because it is not possible to move things into the dbo schema. No, that is not the case, it's just not possible to move dummy.floob -> dbo.floob if there's already an object named dbo.floob. Note that it may not be a table!
CREATE DATABASE schema_test;
GO
USE schema_test;
GO
CREATE SCHEMA floob AUTHORIZATION dbo;
GO
CREATE TABLE dbo.x(a INT);
CREATE TABLE dbo.y(a INT);
GO
Move all tables from dbo -> floob:
DECLARE
#old SYSNAME = N'dbo',
#new SYSNAME = N'floob',
#sql NVARCHAR(MAX) = N'';
SELECT #sql += CHAR(13) + CHAR(10) + 'ALTER SCHEMA ' + #new
+ ' TRANSFER ' + QUOTENAME(SCHEMA_NAME([schema_id]))
+ '.' + QUOTENAME(name) + ';'
FROM sys.tables AS t
WHERE SCHEMA_NAME([schema_id]) = #old
AND NOT EXISTS (SELECT 1 FROM sys.tables WHERE name = t.name
AND SCHEMA_NAME([schema_id]) = #new);
EXEC sp_executesql #sql;
GO
SELECT SCHEMA_NAME([schema_id]),name FROM sys.tables;
Results:
Move all tables back from floob -> dbo:
DECLARE
#old SYSNAME = N'floob',
#new SYSNAME = N'dbo',
#sql NVARCHAR(MAX) = N'';
SELECT #sql += CHAR(13) + CHAR(10) + 'ALTER SCHEMA ' + #new
+ ' TRANSFER ' + QUOTENAME(SCHEMA_NAME([schema_id]))
+ '.' + QUOTENAME(name) + ';'
FROM sys.tables AS t
WHERE SCHEMA_NAME([schema_id]) = #old
AND NOT EXISTS (SELECT 1 FROM sys.tables WHERE name = t.name
AND SCHEMA_NAME([schema_id]) = #new);
EXEC sp_executesql #sql;
GO
SELECT SCHEMA_NAME([schema_id]),name FROM sys.tables;
Results: