SQL Server 2008 R2 Cross Database Ownership Chaining Not working? - sql-server

I have restored two SQL Server 2005 dbs (DB1 & DB2) to a new box running SQL Server 2008 R2.
All objects are owned by dbo
I have a stored procedure DB1.dbo.mp_SPTest. I have given execute permissions to SQLUser1.
CREATE PROCEDURE mp_SPTest
AS
SELECT DB2.dbo.mf_UserHasAccess("BasicUser", "bob")
mp_SPTest calls a scalar function in DB2 DB2.dbo.mf_UserHasAccess(), this function checks if the username passed is a member of a SQL Role.....
CREATE FUNCTION [dbo].[mf_UserHasAccess] (#RoleName varchar(50), #UserName varchar(128))
RETURNS bit
AS
BEGIN
DECLARE #Result bit
SELECT #Result = 1
WHERE #RoleName IN (
SELECT CASE
WHEN (usg.uid is null) THEN 'public'
ELSE usg.name
END AS RoleName
FROM dbo.sysusers usu
LEFT OUTER JOIN (dbo.sysmembers mem
INNER JOIN dbo.sysusers usg
ON mem.groupuid = usg.uid)
ON usu.uid = mem.memberuid
LEFT OUTER JOIN master.dbo.syslogins lo
ON usu.sid = lo.sid
WHERE
(usu.islogin = 1 AND usu.isaliased = 0 AND usu.hasdbaccess = 1)
AND (usg.issqlrole = 1 OR usg.uid is NULL)
AND usu.name = #UserName)
IF #Result <> 1
BEGIN
SET #Result = 0
END
RETURN #Result
END
When I run this procedure as "SQLUser1" it tells me that bob is not a member of BasicUser but when I run it as "sa" it tells me that he IS a member.
As I understand it... because both procedure and function are owned by dbo then that is the context that the function in test2 db would run, therefore it should have access to the same user and login tables.
This worked fine on SQL Server 2005, cant figure it out.
Hope this makes sense, thanks in advance.

Most likely the old SQL Server 2005 had the cross db ownership chaining option turned on, while the new SQL Server 2008 R2 instance has the option left at its default value (off).
But your assumption that 'dbo' in DB1 equate to 'dbo' in DB2 is wrong. 'dbo' in DB1 is the login who corresponds to the owner_sid of DB1 in sys.databases. 'dbo' in DB2 is, likewise, the login that corrsponds to the onwer_sid in sys.databases for DB2. If the two logins are different (if the owner_sid of the two databases is different) then very likely 'dbo' of DB1 will map to some other user and the ownership chain is broken, even if enabled to cross databases. Running ALTER AUTHORIZATION ON DATABASE::[DB..] TO [sa] would fix this problem (ie. it would force the owner_sid to match).
And finally, what you're doing is fundamentally flawed, as it relies on activating ownership chaining across databases, which is a huge security hole, see Potential Threats. A much better avenue is to use code signing.

I solved this identical problem by making the schema the view or procedure is running under the owner of the schemas in the databases it needs to access.
USE [TargetDB]
ALTER AUTHORIZATION ON SCHEMA::[TargetSchema] TO [SourceSchema]
For example
USE [DB1]
ALTER AUTHORIZATION ON SCHEMA::[mem] TO [dbo]
GO
Would allow a view run as DB2.DBO.view assuming chaining is turned on, to access a table in DB1.mem.table. Essentially cross db chaining causes it to access the target DB AS the schema the view is under, not the user who owns the database.

Related

SQL Server : procedure to transfer A server to any server

There are 10 different databases and when I add a procedure to the database I am developing, I need to add it to the other 9 databases one by one.
Do I have a chance to add the newly added procedure to other databases by keeping the strings of the database on the switch case side and giving the site number along with the procedure?
IF EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.ROUTINES
WHERE ROUTINE_NAME = 'spTestProcedure'
AND ROUTINE_SCHEMA = 'dbo'
AND ROUTINE_TYPE = 'PROCEDURE')
This is how I can access the procedure I have added and can I add it to other databases with the create or alter method, if I can, how can I add it?
I would suggest using:
CREATE OR ALTER PROCEDURE ...
to create(if procedure does not exist) and alter(to override if it exists).
As for running the same code on multiple server/databases at once the easiest way is to use Registered Servers in SSMS.

Determine database permissions for database users with valid server logins

I'm looking to find a script that will provide me with the details of what permissions our current SQL server logins have. I am able to script out the current permissions of our database users. The issue I have is that a lot of the database users no longer have a corresponding SQL server login due to a variety of reasons such as domain migrations & staff leaving the company.
The script used to get the database user permissions is : <credit to Nag Pal MCTS/MCITP (SQL Server 2005/2008) this was taken from the old MSDN forum>
DECLARE #DBuser_sql VARCHAR(4000)
DECLARE #DBuser_table TABLE (DBName VARCHAR(200), UserName VARCHAR(250), LoginType VARCHAR(500), AssociatedRole VARCHAR(200))
SET #DBuser_sql='SELECT ''?'' AS DBName,a.name AS Name,a.type_desc AS LoginType,USER_NAME(b.role_principal_id) AS AssociatedRole FROM ?.sys.database_principals a
LEFT OUTER JOIN ?.sys.database_role_members b ON a.principal_id=b.member_principal_id
WHERE a.sid NOT IN (0x01,0x00) AND a.sid IS NOT NULL AND a.type NOT IN (''C'') AND a.is_fixed_role <> 1 AND a.name NOT LIKE ''##%'' AND ''?'' NOT IN (''master'',''msdb'',''model'',''tempdb'') ORDER BY Name'
INSERT #DBuser_table
EXEC sp_MSforeachdb #command1=#dbuser_sql
SELECT * FROM #DBuser_table ORDER BY DBName
Thank you

Remote Call Compiles SPROC differently to local, causes error

I have a stored proc that returns details of SQL Agent Jobs on the local server. There is a master script that calls this proc, using OPENQUERY, against every SQL server in the ecosystem. In pseudocode, the master script looks something like this:
FOR EACH #LinkedServer in the list
SET #SQL = 'INSERT #Results SELECT * FROM OPENQUERY(' + #LinkedServer + ',''EXEC ScriptToGetAgentJobInfo'')'
EXEC sp_executesql #SQL
NEXT #LinkedServer
Some of the agent jobs are created from SSRS report subscriptions, so they have horrible looking names. In order to replace them with the name of the report that is the target of the subscription, I appeal to the ReportServer database on the #LinkedServer, as part of the ScriptToGetAgentJobInfo.
However, not every server contains a ReportServer database, so sometimes this appeal would fail. To get round that failure, I have the following lines of script:
DECLARE #Reports TABLE
( AgentJob SYSNAME
,Reportname NVARCHAR(128));
IF EXISTS(SELECT 1 FROM [master].[dbo].sysdatabases WHERE [name] = 'ReportServer')
BEGIN;
INSERT #Reports(AgentJob,Reportname)
SELECT Job.job_id, Report.[Name]
FROM
ReportServer.dbo.ReportSchedule AS ReportSched
INNER JOIN dbo.sysjobs AS Job ON CONVERT(SYSNAME,ReportSched.ScheduleID) = Job.[name]
INNER JOIN ReportServer.dbo.Subscriptions AS Subscription ON ReportSched.SubscriptionID = Subscription.SubscriptionID
INNER JOIN ReportServer.dbo.[Catalog] AS Report ON Subscription.report_oid = Report.itemid;
END;
The idea is that if the reportserver database doesn't exist, I can avoid any calls to it, which would error, but if it does, I can get data from it. I then join the #Reports table to my SQL Agent job query with a LEFT JOIN to show the name of the relevant report if there is one.
All this works fine when I run the script locally, but when it is called through the master procedure, I get an error saying Invalid object name 'ReportServer.dbo.ReportSchedule'.
I can get round this problem by making the reportserver select statement "dynamic" (although it is totally static) and calling it with another sp_executesql call, but I really hate doing this!
So my question is this: Why does the error only occur when calling the script remotely and how can I avoid it without recourse to dynamic sql?
The master script is written and run in SQL Server 14.0, while the linked server that is causing the problem is only on SQL Server 10.50.

How to create SSIS package for deleting records by joining two tables of different servers without using linked servers [duplicate]

Any suggestions how to join tables from different servers in stored procedure?
Without more details, it's hard to give direct examples, but here is the basic idea:
First, outside of the stored procedure, the host server (the server the stored procedure will be on) has to know about the second server, including (possibly) login information.
On your main server, run the sp_addlinkedserver stored procedure. This only has to be done once:
exec sp_addlinkedserver #server='(your second server)';
If you need to provide login information to this second server (for example, the process can't log in with the same credentials that are used in the initial database connection), do so with the sp_addlinkedsrvlogin stored proc:
exec sp_addlinkedsrvlogin #rmtsrvname='(your second server)',
#useself=false,
#rmtuser='yourusername',
#rmtpassword='yourpassword';
Then, in your stored procedure, you can specify tables on the second server:
SELECT table1.*
FROM table1
INNER JOIN [secondserver].[database].[schema].[table] AS table2 ON
table1.joinfield = table2.joinfield
1. Check to see if you have any linked servers using exec sp_helpserver
2. If your server is not returned then it is not Linked meaning you will need to add it. Otherwise move to step 3.
For Sql Server 2008 R2, go to Server Object > Linked Servers > Add new Linked Server
Or
exec sp_addlinkedserver #server='ServerName';
3. Connect to the Secondary server like so...
exec sp_addlinkedsrvlogin
#rmtsrvname='ServerName'
, #useself=false
, #rmtuser='user'
, #rmtpassword='Password';
4. Now you can Join the tables for the two different servers.
SELECT
SRV1.*
FROM
DB1.database_name.dbo.table_name SRV1
INNER JOIN DB2.database_name.dbo.table_name SRV2
ON SRV1.columnId = SRV2.columnId
GO
You have to first link two servers before joining the tables. Once they are linked, you can just use the below query and replace server, database & table names.
Remember to execute the below sql in DB2:
EXEC sp_addlinkedserver DB1
GO
-- below statement connects sa account of DB2 to DB1
EXEC sp_addlinkedsrvlogin #rmtsrvname = 'DB1', #useself = 'false', #locallogin = 'sa', #rmtuser = 'sa', #rmtpassword = 'DB1 sa pwd'
GO
SELECT a.columns
FROM DB1.database_name.dbo.table_name a
INNER JOIN DB2.database_name.dbo.table_name b
ON a.columnId = b.columnId
GO
Linking servers - http://msdn.microsoft.com/en-us/library/ms188279.aspx
You can write query as below syntax to join other server in SQL Server
SELECT table_1.*
FROM [Database_1].[dbo].[Table_1] table_1
INNER JOIN [IP_OF_SERVER_2].[Database_2].[dbo].[Table_2] table_2 ON table_1.tablekey COLLATE DATABASE_DEFAULT = table_2.tablekey COLLATE DATABASE_DEFAULT
p/s: COLLATE DATABASE_DEFAULT to encode,
Prevent bellow error
Cannot resolve the collation conflict between "Vietnamese_CI_AS" and "SQL_Latin1_General_CP1_CI_AS" in the equal to operation.

Determining the current security checks being made (SQL Server)

One thing that I've always hated more than just about anything in MS SQL Server is the way that security works. The security context constantly switches if you look at the server funny and it's often very hard (for me anyway) to predict or debug.
In dealing with an issue today, I though, "I wish I could just add a line to my code that would display the security context that SQL Server is using when this code runs." Does such a command exist? For example, SELECT security_context()
To be a little clearer... if I'm in a stored procedure and am therefor subject to the security context of the owner of the SP then I'd like to see that. If I'm in code that was called by sp_executesql and it's causing the security to be under the context of the SQL Server service account, then I would want to see that.
At least then I might be able to figure out why SQL Server thinks that I shouldn't have access to something.
Thanks!
EXAMPLE
-- Set up
CREATE USER Test_User WITHOUT LOGIN
CREATE TABLE Test_Security_Context (my_id INT)
INSERT INTO Test_Security_Context VALUES (1)
DENY SELECT ON Test_Security_Context TO Test_User
GO
CREATE PROCEDURE Test_Security_Context_SP
AS
SELECT SUSER_SNAME()
SELECT * FROM Test_Security_Context -- This will return ok
EXEC('SELECT SUSER_SNAME(); SELECT * FROM Test_Security_Context') -- SUSER_SNAME() will match above but select fails
GO
GRANT EXECUTE ON Test_Security_Context_SP TO Test_User
GO
-- Switch to the new user
SETUSER 'Test_User'
GO
-- Do the test
EXEC Test_Security_Context_SP
GO
-- Clean up
SETUSER
DROP PROCEDURE Test_Security_Context_SP
DROP TABLE Test_Security_Context
DROP USER Test_User
GO
Yes, there is such a pair of views that represents your current security context, considering all the details like EXECUTE AS or code signing:
sys.login_token for the server wide context
sys.user_token for the current database context
Every single access you get is ultimately derived from a row in the return of these results. Note that some access are implicit from hard coded role membership (like db_datareader database role or sysadmin server role).
Other that that:
ownership chaining is not related to security context: you are not under the 'context' of the SP owner. Ownership chaining simply states that access checks are skipped for objects owned by the same owner as current object (SP, View).
sp_executesql does not change the security context in any way
Not sure if this is what you mean by security context, but you can retrieve the user associated with your session like:
select SYSTEM_USER
This works for both a SQL Server login or a WIndows login. It even works inside stored procedures with execute as owner. For example,
create procedure dbo.Test
with execute as owner
as
select SYSTEM_USER
go
exec dbo.Test
select SYSTEM_USER
Prints:
sa
MyMachine\MyName
If you're looking for the Windows account that SQL Server is using to do things on your behalf, you could try to run whoami from the command like:
EXEC sp_configure 'show advanced options', 1
RECONFIGURE
EXEC sp_configure 'xp_cmdshell', 1
RECONFIGURE
EXEC master..xp_cmdshell 'whoami'
For me, that returns nt authority\network service.
I think you want to use CURRENT_USER to see the current security context. Here's an example:
SELECT CURRENT_USER AS 'Current User Name';
GO
EXECUTE AS LOGIN = 'junk'
GO
SELECT CURRENT_USER AS 'Current User Name';
GO
REVERT
SELECT CURRENT_USER AS 'Current User Name';
GO
with output (note: I'm admin on my SQL Server for this)
Current User Name
------------------
dbo
(1 row(s) affected)
Current User Name
------------------
Junk
(1 row(s) affected)
Current User Name
------------------
dbo
(1 row(s) affected)

Resources