登陸賬戶及權限變更的審計
因爲系統視圖sys.server_permissions沒有記錄登陸賬戶權限變更的時間,同時爲了更及時、全面的瞭解到登陸賬戶及權限變更的信息,保障數據庫的安全,使用DDL觸發器事件 DDL_LOGIN_EVENTS、DDL_GDR_SERVER_EVENTS、ADD_SERVER_ROLE_MEMBER、ALTER_SERVER_ROLE、CREATE_SERVER_ROLE(後面兩個事件適用於SQL Server 2012及以上版本)記錄登陸賬戶的變更信息,並郵件通知管理人員。
下面先創建登陸賬戶變更信息記錄表:
USE master
GO
CREATE TABLE dbo.loginAudit_log
(
id INT IDENTITY(1, 1)
,Posttime DATETIME NULL
,Servername VARCHAR(30) NULL
,Hostname VARCHAR(30) NULL
,Loginame VARCHAR(30) NULL
,Logintype VARCHAR(20) NULL
,ClientHost VARCHAR(20) NULL
,DDLType VARCHAR(30) NULL
,Grantor SYSNAME NULL
,Grantee SYSNAME NULL
,Permission VARCHAR(30) NULL
,ObjectName SYSNAME NULL
,ObjectType VARCHAR(30) NULL
,GrantOption BIT NULL
,CascadeOption BIT NULL
,TSQLCommand NVARCHAR(800) NULL
)
接着創建服務器及觸發器,記錄並警告登陸賬戶及權限變更:
USE master
go
CREATE TRIGGER tr_LoginPermission_Audit ON ALL SERVER
WITH EXECUTE AS 'sa'
FOR DDL_LOGIN_EVENTS, DDL_GDR_SERVER_EVENTS,ADD_SERVER_ROLE_MEMBER
--,ALTER_SERVER_ROLE,CREATE_SERVER_ROLE --適用於SQL Server 2012及以上版本
AS
BEGIN
DECLARE @Data XML
DECLARE @PostTime NVARCHAR(24)
DECLARE @LoginName NVARCHAR(100)
DECLARE @HostName NVARCHAR(100)
DECLARE @ServerName NVARCHAR(20)
DECLARE @LoginType NVARCHAR(20)
DECLARE @ClientHost NVARCHAR(20)
DECLARE @DDLType VARCHAR(30)
DECLARE @Grantor SYSNAME
DECLARE @Grantee SYSNAME
DECLARE @Permission VARCHAR(30)
DECLARE @ObjectName SYSNAME
DECLARE @ObjectType VARCHAR(30)
DECLARE @GrantOption BIT
DECLARE @CascadeOption BIT
DECLARE @TSQLCommand nVARCHAR(800)
SET @Data = EVENTDATA()
SET @HostName = HOST_NAME()
SET @PostTime = @Data.value('(/EVENT_INSTANCE/PostTime)[1]', 'NVARCHAR(24)')
SET @LoginName = @Data.value('(/EVENT_INSTANCE/LoginName)[1]', 'NVARCHAR(100)')
SET @ServerName = @Data.value('(/EVENT_INSTANCE/ServerName)[1]', 'NVARCHAR(100)')
SET @ClientHost = @Data.value('(/EVENT_INSTANCE/ClientHost)[1]', 'NVARCHAR(100)')
SET @LoginType = @Data.value('(/EVENT_INSTANCE/LoginType)[1]', 'nvarchar(100)')
SET @DDLType = @Data.value('(/EVENT_INSTANCE/EventType)[1]', 'nvarchar(100)')
SET @Grantor = @Data.value('(/EVENT_INSTANCE/Grantor)[1]', 'nvarchar(100)')
SET @Grantee = @Data.value('(/EVENT_INSTANCE/Grantees/Grantee)[1]', 'nvarchar(100)')
SET @Permission = @Data.value('(/EVENT_INSTANCE/Permissions/Permission)[1]', 'nvarchar(100)')
SET @ObjectName = @Data.value('(/EVENT_INSTANCE/ObjectName)[1]', 'nvarchar(100)')
SET @ObjectType = @Data.value('(/EVENT_INSTANCE/ObjectType)[1]', 'nvarchar(100)')
SET @GrantOption = @Data.value('(/EVENT_INSTANCE/GrantOption)[1]', 'nvarchar(100)')
SET @CascadeOption = @Data.value('(/EVENT_INSTANCE/CascadeOption)[1]', 'nvarchar(5)')
SET @TSQLCommand = @Data.value('(/EVENT_INSTANCE/TSQLCommand/CommandText)[1]', 'nvarchar(800)')
--IF @ClientHost <> '<local machine>'
--BEGIN
INSERT INTO loginAudit_log ( Posttime, Servername, Hostname, Loginame, Logintype, ClientHost,
DDLType, Grantor, Grantee, Permission, ObjectName, ObjectType,
GrantOption, CascadeOption, TSQLCommand )
VALUES ( @posttime, @ServerName, @hostname, @loginname, @LoginType, @ClientHost, @DDLType, @Grantor,
@Grantee, @Permission, @ObjectName, @ObjectType, @GrantOption, @CascadeOption,
@TSQLCommand )
EXEC msdb.dbo.sp_send_dbmail @profile_name = 'dbmail',
@recipients = '[email protected]',
@subject = N'登陸帳戶及權限變更',
@body = @TSQLCommand
--END
END;
注意:在創建觸發器時,要修改郵件的配置文件名稱,及接收人。
測試登陸賬戶及權限變更審計觸發器
測試腳本
CREATE LOGIN [Jack] WITH PASSWORD=N'Password'
, DEFAULT_DATABASE=[master]
, DEFAULT_LANGUAGE=[簡體中文]
, CHECK_EXPIRATION=OFF
, CHECK_POLICY=OFF
GO
ALTER LOGIN [Jack] DISABLE
GO
ALTER LOGIN [Jack] ENABLE
GO
GRANT VIEW ANY DATABASE TO Jack WITH GRANT OPTION
REVOKE VIEW ANY DATABASE FROM Jack
DENY VIEW ANY DATABASE TO Jack CASCADE
EXEC sys.sp_addsrvrolemember Jack,sysadmin
EXEC sys.sp_dropsrvrolemember Jack,sysadmin
IF EXISTS (SELECT * FROM sys.server_principals WHERE name = N'Jack')
DROP LOGIN [Jack]
測試結果
注意:在SQL Server 2008中,使用sys.sp_dropsrvrolemember刪除固定角色成員無法觸發。
EXEC sys.sp_dropsrvrolemember Jack,sysadmin
在SQL Server 2016中創建觸發器tr_LoginPermission_Audit時,啓用事件ALTER_SERVER_ROLE,CREATE_SERVER_ROLE,並進行如下測試:
--SQL Server 2016 測試創建服務器角色、賦予權限、添加成員、刪除成員、刪除用戶創建服務器角色
USE [master]
GO
CREATE SERVER ROLE [serverRoleName]
GO
GRANT VIEW ANY DATABASE TO serverRoleName
DENY CONTROL SERVER TO serverRoleName
EXEC sp_addsrvrolemember Jack_login, serverRoleName
--先刪除用戶創建的角色的所有成員,再刪除用戶創建的服務器角色
DECLARE @RoleName SYSNAME
SET @RoleName = N'serverRoleName'
IF @RoleName <> N'public' and (select is_fixed_role from sys.server_principals where name = @RoleName) = 0
BEGIN
DECLARE @RoleMemberName sysname
DECLARE Member_Cursor CURSOR FOR
select [name]
from sys.server_principals
where principal_id in (
select member_principal_id
from sys.server_role_members
where role_principal_id in (
select principal_id
FROM sys.server_principals where [name] = @RoleName AND type = 'R' ))
OPEN Member_Cursor;
FETCH NEXT FROM Member_Cursor
into @RoleMemberName
DECLARE @SQL NVARCHAR(4000)
WHILE @@FETCH_STATUS = 0
BEGIN
SET @SQL = 'ALTER SERVER ROLE '+ QUOTENAME(@RoleName,'[') +' DROP MEMBER '+ QUOTENAME(@RoleMemberName,'[')
EXEC(@SQL)
FETCH NEXT FROM Member_Cursor
into @RoleMemberName
END;
CLOSE Member_Cursor;
DEALLOCATE Member_Cursor;
END
DROP SERVER ROLE [serverRoleName]
GO
測試結果:
從測試結果可以看出,刪除用戶創建的服務器角色、刪除用戶創建的服務器角色的成員均不能觸發。
數據庫用戶(包含用戶)及權限變更的審計
同上面登陸賬戶情景一樣,下面給出了數據庫用戶及權限變更信息記錄日誌表:
USE master
GO
CREATE TABLE dbo.UserAudit_Log
(
Id INT IDENTITY(1, 1)
,Posttime DATETIME NULL
,Servername sysname NULL
,Hostname sysname NULL
,Loginame sysname NULL
,UserName sysname NULL
,ClientHost VARCHAR(20) NULL
,DBName sysname NULL
,SchemaName sysname NULL
,DDLType VARCHAR(30) NULL
,Grantor SYSNAME NULL
,Grantee SYSNAME NULL
,Permission VARCHAR(30) NULL
,AsGrantor sysname NULL
,ObjectName SYSNAME NULL
,ObjectType VARCHAR(30) NULL
,GrantOption BIT NULL
,CascadeOption BIT NULL
,TSQLCommand NVARCHAR(800) NULL
)
--DefaultSchema
--RoleName
和服務器觸發器不同,接下來我們需要在各數據庫中創建數據庫及觸發器,如下給出了在數據庫test下創建的觸發器Tr_UserPermission_Audit:
USE test
go
CREATE TRIGGER Tr_UserPermission_Audit ON DATABASE
FOR DDL_APPLICATION_ROLE_EVENTS,DDL_GDR_DATABASE_EVENTS
,DDL_ROLE_EVENTS,DDL_USER_EVENTS
AS
BEGIN
DECLARE @Data XML
DECLARE @PostTime NVARCHAR(24)
DECLARE @LoginName sysname
DECLARE @HostName sysname
DECLARE @ServerName sysname
DECLARE @ClientHost NVARCHAR(20)
DECLARE @UserName sysname
DECLARE @DBName sysname
DECLARE @SchemaName sysname
DECLARE @DDLType VARCHAR(30)
DECLARE @Grantor SYSNAME
DECLARE @Grantee SYSNAME
DECLARE @AsGrantor sysname
DECLARE @Permission VARCHAR(30)
DECLARE @ObjectName SYSNAME
DECLARE @ObjectType VARCHAR(30)
DECLARE @GrantOption BIT
DECLARE @CascadeOption BIT
DECLARE @TSQLCommand nVARCHAR(800)
SET @Data = EVENTDATA()
SET @HostName = HOST_NAME()
SET @PostTime = @Data.value('(/EVENT_INSTANCE/PostTime)[1]', 'NVARCHAR(24)')
SET @LoginName = @Data.value('(/EVENT_INSTANCE/LoginName)[1]', 'NVARCHAR(100)')
SET @ServerName = @Data.value('(/EVENT_INSTANCE/ServerName)[1]', 'NVARCHAR(100)')
SET @[email protected]('(/EVENT_INSTANCE/DatabaseName)[1]', 'NVARCHAR(100)')
SET @[email protected]('(/EVENT_INSTANCE/SchemaName )[1]', 'NVARCHAR(100)')
IF @SchemaName='' OR @SchemaName is NULL
SET @[email protected]('(/EVENT_INSTANCE/DefaultSchema)[1]', 'NVARCHAR(100)')
SET @[email protected]('(/EVENT_INSTANCE/UserName)[1]', 'NVARCHAR(100)')
SET @ClientHost = @Data.value('(/EVENT_INSTANCE/ClientHost)[1]', 'NVARCHAR(100)')
SET @DDLType = @Data.value('(/EVENT_INSTANCE/EventType)[1]', 'nvarchar(100)')
SET @Grantor = @Data.value('(/EVENT_INSTANCE/Grantor)[1]', 'nvarchar(100)')
SET @Grantee = @Data.value('(/EVENT_INSTANCE/Grantees/Grantee)[1]', 'nvarchar(100)')
SET @[email protected]('(/EVENT_INSTANCE/AsGrantor)[1]', 'nvarchar(100)')
SET @Permission = @Data.value('(/EVENT_INSTANCE/Permissions/Permission)[1]', 'nvarchar(100)')
SET @ObjectName = @Data.value('(/EVENT_INSTANCE/ObjectName)[1]', 'nvarchar(100)')
SET @ObjectType = @Data.value('(/EVENT_INSTANCE/ObjectType)[1]', 'nvarchar(100)')
SET @GrantOption = @Data.value('(/EVENT_INSTANCE/GrantOption)[1]', 'nvarchar(100)')
SET @CascadeOption = @Data.value('(/EVENT_INSTANCE/CascadeOption)[1]', 'nvarchar(5)')
SET @TSQLCommand = @Data.value('(/EVENT_INSTANCE/TSQLCommand/CommandText)[1]', 'nvarchar(800)')
--IF @ClientHost <> '<local machine>'
--BEGIN
INSERT INTO MASTER.dbo.UserAudit_Log ( Posttime, Servername, Hostname, Loginame, ClientHost,UserName,DBName,SchemaName,
DDLType, Grantor, Grantee,AsGrantor, Permission, ObjectName, ObjectType,
GrantOption, CascadeOption, TSQLCommand )
VALUES ( @posttime, @ServerName, @hostname, @loginname, @ClientHost,@UserName,@DBName,@SchemaName, @DDLType, @Grantor,
@Grantee,@AsGrantor, @Permission, @ObjectName, @ObjectType, @GrantOption, @CascadeOption,
@TSQLCommand )
DECLARE @bodytxt nvarchar(MAX)
SET @bodytxt=N'數據庫'+@DBName+N'變更TSQL:'+CHAR(10)+@TSQLCommand
EXEC msdb.dbo.sp_send_dbmail @profile_name = 'dbmail',
@recipients = '[email protected]',
@subject = N'數據庫用戶\角色及權限變更',
@body = @bodytxt
--END
END;
注意:1. 在創建此觸發器時同樣需要修改郵件的配置文件名稱及接收人
2. 在每個需要監控的數據庫下均要創建此觸發器
如下給出簡單的測試樣例
--SQL Server 2008測試樣例
USE test
GO
CREATE USER Jack FOR LOGIN Jack
CREATE USER Jack1 WITHOUT LOGIN
ALTER USER Jack1 WITH DEFAULT_SCHEMA=test
GRANT ALTER TO Jack WITH GRANT OPTION
REVOKE ALTER TO Jack CASCADE
DENY EXEC ON OBJECT::dbo.SimpleProc TO Jack
GRANT SELECT ON OBJECT::test(a,b) TO Jack
CREATE ROLE [testRoleIntest] AUTHORIZATION [dbo]
GO
DENY EXEC ON OBJECT::dbo.SimpleProc TO [testRoleIntest]
EXEC sys.sp_addrolemember [testRoleIntest],Jack
EXEC sys.sp_droprolemember [testRoleIntest], Jack
ALTER ROLE testRoleIntest WITH NAME=testRole;
CREATE APPLICATION ROLE [AppRole] WITH DEFAULT_SCHEMA = [dbo], PASSWORD =N'Pass,12word'
GRANT CONTROL TO [AppRole]
GO
GRANT CONTROL ON APPLICATION ROLE::[AppRole] TO [Jack] AS [AppRole]
GO
ALTER APPLICATION ROLE AppRole WITH PASSWORD=N'New!2Pwd'
DROP USER Jack
DROP USER Jack1
DROP ROLE [testRole]
DROP APPLICATION ROLE [AppRole]
SQL Server 2012 及以上測試環境測試樣例:
--SQL Server 2012及以上環境
CREATE USER Jack WITH password='Password,@1'
ALTER USER Jack WITH ALLOW_ENCRYPTED_VALUE_MODIFICATIONS = ON
DROP USER Jack
CREATE ROLE testRole
ALTER ROLE testRole ADD member Jack
ALTER ROLE testRole DROP member Jack
測試結果
從測試結果可以看到,創建完觸發器後,我們可以及時收到登陸賬戶、數據庫用戶及權限變更的預警,確保數據庫的安全。