T-SQL Auditing Script to find logins inheriting permission via AD Groups and verify DBO/Database owner login in a single execution
Posted by Sudarshan Narasimhan on March 5, 2012
I had authored a post on this a couple of weeks back to detect mismatch between DBO User & database owner. I had also included how to find the various groups a windows login belongs to using the login’s token in sys.login_token. I continued working on this script and finished it last week. One of the problem’s was the login token view is only useful when you have logged in via the User itself. If you are an admin you will get results from the Admin login token, which isn’t useful in detecting permissions.
So, this new script I wrote will give you the following information, which is very very useful when doing security audits, security permission validation and especially useful if you manage a lot of databases and your SQL Server has a lot of AD Group logins etc. Here is what my script does:-
- DBO Owner Login to Database Owner Mapping.
- Logins which are part of AD Groups and has DBO rights in each database
- Members of DB_OWNER role in each database excluding the DBO user itself (including permissions via AD Groups).
- Summary: Identify those users who are DBO mapped logins, but they do NOT have explicit login NOR are they are part of DB_OWNER role.
You can think of this as a complete security auditing script for AD Logins, groups, Database Owner/DBO validity.
T-SQL SCRIPT for Security Auditing of DBO Access & AD Groups
set nocount on go Create table #TmpTableSec1 (database_name varchar(100), Owner varchar(100)) Create table #TmpTableSec2 (database_name varchar(100), principal varchar(50), DBO_Owner_Login varchar(100)) Create table #TmpResult (database_name varchar(100), principal varchar(50), DBO_Owner_Login varchar(100)) DECLARE DBCURSOR CURSOR FOR select name from sys.databases where state=0 and name not in ('tempdb','model','master','msdb') Declare @name varchar(50) Declare @cmd varchar(200) Declare @dbowner varchar(100) OPEN DBCURSOR FETCH NEXT FROM DBCURSOR INTO @name WHILE @@FETCH_STATUS = 0 BEGIN --print 'Database --> ['+@name+']' set @cmd = 'select name, suser_sname(owner_sid) from master.sys.databases where name = '''+@name+'''' --select @cmd insert #TmpTableSec1 exec (@cmd) --select @dbowner = (select suser_sname(owner_sid) from master.sys.databases where name = @name) set @cmd = 'use '+ @name +' select db_name(), name, suser_sname(sid) from sys.database_principals where name = ''dbo''' --select ''@DBO'' = (select suser_sname(sid) from sys.database_principals where name = ''dbo'') ' INSERT #TmpTableSec2 exec (@cmd) FETCH NEXT FROM DBCURSOR INTO @name END CLOSE DBCURSOR DEALLOCATE DBCURSOR --select * from #TmpTableSec1 --select * from #TmpTableSec2 insert into #TmpResult select a.database_name, a.Owner, b.DBO_Owner_Login from #TmpTableSec1 a join #TmpTableSec2 b on a.database_name = b.database_name --select * from #TmpResult Create table #GroupMemberTable (account_name varchar(200), type varchar(50), privilege varchar(100), login_name varchar(200), group_name varchar(500)) DECLARE DBCURSOR2 CURSOR FOR select distinct(DBO_Owner_Login) from #TmpResult where DBO_Owner_Login like '%\%' and DBO_Owner_Login <> 'NT AUTHORITY\SYSTEM' Declare @DBO varchar (200) Declare @DBNAME varchar (200) OPEN DBCURSOR2 FETCH NEXT FROM DBCURSOR2 INTO @DBO WHILE @@FETCH_STATUS = 0 BEGIN set @cmd = 'EXEC xp_logininfo '''+@DBO+ ''', ''all'' ' insert into #GroupMemberTable EXEC (@cmd) --update #GroupMemberTable set database_name = @DBNAME where id=@@IDENTITY --print @cmd FETCH NEXT FROM DBCURSOR2 INTO @DBO END CLOSE DBCURSOR2 DEALLOCATE DBCURSOR2 --select * from #TmpResult --select * from #GroupMemberTable where group_name <> 'NULL' --and privilege = 'admin' Create table #Tmp_DB_ownerRoleResults (database_name varchar(100), DBOwnerRole_Member_UserName varchar(200), User_type varchar(100)) DECLARE DBCURSOR3 CURSOR FOR select database_name from #TmpResult where DBO_Owner_Login <> 'NT AUTHORITY\SYSTEM' Declare @database_name3 varchar (200) Declare @cmd3 varchar(500) OPEN DBCURSOR3 FETCH NEXT FROM DBCURSOR3 INTO @database_name3 WHILE @@FETCH_STATUS = 0 BEGIN set @cmd3 = 'use '+ @database_name3 +' select db_name(), user_name(member_principal_id), type_desc from sys.database_role_members a inner join sys.database_principals b on a.member_principal_id = b.principal_id where a.role_principal_id=16384' --print @cmd3 --exec (@cmd3) insert into #Tmp_DB_ownerRoleResults exec (@cmd3) FETCH NEXT FROM DBCURSOR3 INTO @database_name3 END CLOSE DBCURSOR3 DEALLOCATE DBCURSOR3 print '###########################################################' print '###### Database Owner and DBO Owner Logging Mapping #######' print '###########################################################' select * from #TmpResult print '###########################################################################' print '###### AD Group Member Logins which have DBO Access in the database #######' print '###########################################################################' create table #TmpTableSec3 (database_name varchar(100), Database_Owner varchar(200), DBO_Owner_Login varchar(200), type varchar (100), privilege varchar(100), group_name varchar(500)) insert into #TmpTableSec3 select a.database_name, a.principal, a.DBO_Owner_Login, b.type, b.privilege, b.group_name from #TmpResult a left join #GroupMemberTable b on a.DBO_Owner_Login = b.login_name where b.group_name is not null --and b.privilege = 'admin' order by a.database_name select database_name, DBO_Owner_Login, privilege, group_name from #TmpTableSec3 where privilege = 'admin' order by database_name asc print '####################################################################################' print '###### Members of DB_OWNER ROLE For Each Database (Excluding DBO Owner Login)#######' print '####################################################################################' Create table #TmpTableSec4 (database_name varchar(100), DBOwnerRole_Member_UserName varchar(200), User_type varchar(100)) insert into #TmpTableSec4 select a.database_name as [Database_Name], b.DBOwnerRole_Member_UserName, b.User_type from #TmpResult a inner join #Tmp_DB_ownerRoleResults b on a.database_name = b.database_name where b.DBOwnerRole_Member_UserName <> 'dbo' Create table #TmpTableSec5 (database_name varchar(100), DBOwnerRole_Member_UserName varchar(200), User_type varchar(100)) insert into #TmpTableSec5 select a.name as [Database_Name], b.DBOwnerRole_Member_UserName, b.User_type from sys.databases a left outer join #TmpTableSec4 b on a.name = b.database_name select * from #TmpTableSec5 where DBOwnerRole_Member_UserName is not null order by database_name print'' print '#########################################################################################################################' print '###### Final Result: Users who are not part of DB_OWNER Role nor having explicit login access but have DBO rights #######' print '#########################################################################################################################' print '' create table #TmpFinalResult (group_name varchar(500), database_name varchar(100), DBO_Owner_Login varchar(200), Database_Owner varchar(200), privilege varchar(100)) insert into #TmpFinalResult select distinct (a.group_name), a.database_name, a.DBO_Owner_Login, a.Database_Owner, a.privilege from #TmpTableSec3 a where a.database_name in (select database_name from #TmpResult where principal <> DBO_Owner_Login) and a.group_name not in (select name from sys.syslogins where sysadmin = 1 and isntgroup = 1) and a.DBO_Owner_Login not in (select name from sys.syslogins where sysadmin = 1 and isntuser = 1) and a.DBO_Owner_Login not in (select DBO_Owner_Login from #TmpTableSec3 where privilege = 'admin') --and a.group_name not in ('Any Domain Groups to be filtered goes here e.g. [DOMAIN\All Users]') order by a.database_name asc select a.database_name, a.DBO_Owner_Login, a.Database_Owner, a.group_name, a.privilege from #TmpFinalResult a order by a.database_name asc drop table #TmpTableSec1 drop table #TmpTableSec2 drop table #TmpTableSec3 drop table #TmpTableSec4 drop table #TmpTableSec5 drop table #TmpResult drop table #GroupMemberTable drop table #Tmp_DB_ownerRoleResults drop table #TmpFinalResult go set nocount off go
Explanation of the Script Output
###########################################################
###### Database Owner and DBO Owner Logging Mapping #######
###########################################################
This section gives you the mapping between the database owner in sys.databases and the login which owns the DBO User in each database. If you move databases between servers, its possible that the old DBO Login doesn’t exist on the new instance and this could cause unauthorized access by the old DBO id he/she has login access to the server, then he/she retains DBO access in the newly moved database.
###########################################################################
###### AD Group Member Logins which have DBO Access in the database #######
###########################################################################
This section presents to you the different Active Directory Groups that have DBO/DB_Owner access in each database. Sometimes, if you have too many AD Groups in different OU’s, for a DBA it gets confusing to manage the authorized access. You can certainly manually look up the AD Users and figure this out. But, being a SQL person I’m sure its a lot more comfortable seeing this within SQL Server itself. This script does just that and shows the diff groups per each database.
####################################################################################
###### Members of DB_OWNER ROLE For Each Database (Excluding DBO Owner Login)#######
####################################################################################
Since we are anyways getting DBO Owner login we might as well detect the different users who are part of DB_OWNER fixed database role. Each member of DB_OWNER role has Full Control on the database. Hence the need to track this information.
#########################################################################################################################
###### Final Result: Users who are not part of DB_OWNER Role nor having explicit login access but have DBO rights #######
#########################################################################################################################
This is the one that does the magic! It gives a final report of each user who has DBO Access, but who isn’t a part of DB_OWNER role nor does this person has sysadmin through any of the AD Groups. It also reports all the AD Group, through which this windows user can get a login access to SQL Server thereby gaining DBO Access when switching context to the database reported against the user’s name. If you see any results here, then its very much a security hole, that needs to be plugged after careful review of this user’s individual object rights.
The commented line on a.group_name in the final section, is for you to add any AD Groups which each user in the organisation is a part of, thereby removing any unnecessary rows from the output.
Have fun Auditing. Please post back your comments on the security script…
This entry was posted on March 5, 2012 at 10:55 PM and is filed under Security. Tagged: Active Directory, AD Groups, Auditing, database owner, DBO Access, login token, security, SQL. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.
Randy said
Having problems with it on Sharepoint databases with database names as long as 70 characters. The database name is getting truncated somehow “Could not locate entry in sysdatabases for database ‘SharePoint_AdminContent_63c2917c’. No entry found with that name. Make sure that the name is entered correctly.”
coolmusings said
Ok, just had to add square brackets around the database name in the “use” commands
Sudarshan Narasimhan said
Yes. you need the [ ] to take care of the special characters 🙂