SQL Server 數據庫管理中,賬戶安全是數據庫安全的一道重要屏障。除對密碼複雜度有嚴格要求外,週期性修改賬戶密碼,是保證數據庫安全的一個重要舉措。如下給出修改登陸賬戶/包含用戶密碼的腳本:
--更換登陸帳戶密碼
USE master
GO
ALTER LOGIN Jack WITH PASSWORD='I8@^mV7(Tyz0Jov',CHECK_POLICY=ON,CHECK_EXPIRATION=OFF
--修改包含用戶密碼
USE test
GO
ALTER USER Jack WITH PASSWORD='I8@^mV7(Tyz0Jov'
但對於生產數據庫,這樣直接修改原賬戶的密碼是存在問題的。因爲我們不能做到數據庫和應用同時修改密碼,這樣是會影響業務的正常運行,而導致不必要的損失。而較爲合理的做法是創建新賬戶,賦予原賬戶相同的權限,替換原賬戶。下面將使用《SQL Server 登陸賬戶權限克隆——sp_DBA_LoginClone 升級版》、《克隆SQL Server 用戶及權限》兩篇文章中敘述的SQL Server登陸賬戶、SQL Server 數據庫用戶克隆的方式給出新賬戶的創建及權限賦予腳本。
爲更方便的給創建新賬戶提供隨機密碼,這裏首先給出生成隨機密碼的腳本,如下:
--創建字符集表,並插入生成密碼的字符
USE master
CREATE TABLE AsciiCode(id INT IDENTITY(1,1),characters CHAR(1))
DECLARE @i INT=33
WHILE @i<127
BEGIN
IF @i NOT IN(34,39) --密碼中不能有單引號和雙引號
INSERT INTO AsciiCode(characters)
SELECT CHAR(@i)
SET @i=@i+1
END
GO
--創建生成隨機密碼的存儲過程
USE master
GO
CREATE PROC pwdGenerate(
@num smallint --密碼長度
,@pwd VARCHAR(50) OUTPUT
)
AS
BEGIN
DECLARE @i SMALLINT=1
SET @pwd=''
WHILE @i<=@num
BEGIN
SELECT TOP 1 @pwd=@pwd+characters
FROM master.dbo.AsciiCode
--WHERE id=RAND()*94
ORDER BY NEWID()
SET @i=@i+1
END
--RETURN @pwd
END
GO
替換賬戶信息
接下來確定需要替換的賬戶、新賬戶,並使用上面的腳本生成密碼,如下:
USE master
GO
--創建新舊帳戶密碼對應表
CREATE TABLE login_create
(
id INT IDENTITY(1,1)
,newLoginName sysname NULL
,loginName sysname NULL
,pwd VARCHAR(50)
,dbName sysname NULL
,userName sysname NULL
,newUserName sysname NULL
)
----插入需要替換的賬戶(一般針對生產環境使用的帳戶、用戶)、新帳戶、密碼(密碼可以由存儲過程pwdGenerate生成)
INSERT INTO login_create(loginName)
SELECT name
FROM sys.sql_logins
WHERE name NOT IN ( 'NT SERVICE\MSSQLSERVER', 'NT AUTHORITY\SYSTEM', 'NT SERVICE\SQLSERVERAGENT','##MS_PolicyTsqlExecutionLogin##','##MS_PolicyEventProcessingLogin##' )
AND is_disabled=0
--select * from login_create
--TRUNCATE TABLE login_create
UPDATE login_create SET newLoginName=loginName+'_V1'
--生成新帳戶的密碼
DECLARE @id INT=1,@maxNum INT
DECLARE @pwd VARCHAR(50)
SELECT
@maxNum=MAX(id)
from login_create
WHILE @id<=@maxNum
BEGIN
EXEC pwdGenerate 15,@pwd OUTPUT
UPDATE login_create SET pwd=@pwd
WHERE id=@id
SET @id=@id+1
END
GO
注意:login_create中,對於登陸賬戶,DBName、userName、newUserName字段爲NULL;包含用戶newLoginName、loginName爲空。
克隆賬戶用戶
使用前文中的過程sp_DBA_LoginPermissionsClone、sp_DBA_userPermisionsClone,生成新賬戶的創建腳本,及權限賦予腳本。注意執行下面腳本時需要先在master庫下創建sp_DBA_LoginPermissionsClone、sp_DBA_userPermisionsClone兩過程,並標記爲系統過程。
USE master
GO
--生成新帳戶的創建腳本,並生成克隆原帳戶權限的賦予腳本
SET nocount ON
DECLARE @newLoginName SYSNAME
,@loginName SYSNAME
,@pwd CHAR(15)
,@userName sysname
,@dbName sysname
,@newUserName sysname
,@sql NVARCHAR(max)
DECLARE logins CURSOR
FOR
SELECT newLoginName,loginName,pwd,dbName,userName,newUserName
FROM login_create
OPEN logins
FETCH NEXT FROM logins INTO @newLoginName, @loginName, @pwd,@dbName,@userName,@newUserName
--循環登陸賬戶
WHILE @@FETCH_STATUS = 0
BEGIN
if @loginName is not null
begin
EXEC dbo.sp_DBA_LoginPermissionsClone @loginName,@newLoginName,@pwd,@sql OUTPUT
PRINT @sql
set @userName=@loginName
end
--循環數據庫,生成創建數據用戶腳本及賦予用戶權限腳本
DECLARE @databaseName SYSNAME
DECLARE databaseNames CURSOR
FOR
SELECT name
FROM sys.sysdatabases
OPEN databaseNames
FETCH NEXT FROM databaseNames INTO @databaseName
WHILE @@FETCH_STATUS = 0
BEGIN
SET @sql = N'select @i=1 from ' + QUOTENAME(@databaseName,'[') + N'.sys.sysusers
where name=''' + @userName + ''''
DECLARE @j INT= NULL
EXEC sp_executesql @sql, N'@i int output', @i = @j OUTPUT
IF ( @j IS NULL ) --數據庫不存在用戶,回到開始進行下次循環
BEGIN
FETCH NEXT FROM databaseNames INTO @databaseName
CONTINUE
END
IF @userName IS NULL
SET @userName=@loginName
IF @newUserName IS NULL
SET @newUserName=@newLoginName
IF ( @j = 1 )
BEGIN
if (@dbName is not null and @dbName=@databaseName) or @dbName is null
EXEC dbo.sp_DBA_userPermisionsClone @databaseName,@userName,@newUserName,@newLoginName,@pwd
END
FETCH NEXT FROM databaseNames INTO @databaseName
END
CLOSE databaseNames
DEALLOCATE databaseNames
FETCH NEXT FROM logins INTO @newLoginName, @loginName, @pwd,@dbName,@userName,@newUserName
END
CLOSE logins
DEALLOCATE logins
注意:對於包含用戶,如果存在涉及到跨庫的包含用戶,需要單獨創建,具體參加《SQL Server 部分包含數據庫》。
生成完腳本後,及時備份創建腳本或者執行生成的腳本。將新、舊賬戶對應關係及密碼導出到本機加密文件中,更新密碼字段爲NULL。同時檢查附近時點是否有對該庫進行備份的作業,確保新賬戶密碼沒有備份到數據庫備份文件中。
UPDATE login_create SET pwd=NULL
監控賬戶替換情況
新賬戶創建完成後,需要將舊、新賬戶及密碼發相關人員替換(開發或者IT),開啓就賬戶連接監控。我們可以執行使用查詢連接的腳本,查看哪些舊賬戶還沒有被替換掉:
--查詢指定連接數據庫賬戶的IP
SELECT distinct client_net_address,loginame,hostname,program_name
FROM sys.dm_exec_connections conn
join master..sysprocesses procs
on conn.session_id=procs.spid
where loginame IN(SELECT loginName from login_create)
但對於哪些非頻繁連接的賬戶,使用上面的查詢並不一定能捕獲到,最好的方式是使用觸發器來監控:
--創建登陸信息日誌表
CREATE TABLE [dbo].[Login_Monitor](
[Spid] [int] NULL,
[Posttime] [datetime] NULL,
[Servername] [varchar](30) NULL,
[Hostname] [varchar](30) NULL,
[Loginame] [varchar](30) NULL,
[Logintype] [varchar](20) NULL,
[ClientHost] [varchar](20) NULL,
[Stat] [int] NULL
)
use master
GO
--==================================
-- 服務器登監控賬戶信息
--==================================
CREATE TRIGGER [LoginMonitor]
ON all server WITH EXECUTE AS 'sa'
FOR LOGON
AS
BEGIN
DECLARE @Data XML
DECLARE @PostTime NVARCHAR(24)
DECLARE @Spid NVARCHAR(6)
DECLARE @LoginName NVARCHAR(100)
DECLARE @HostName NVARCHAR(100)
DECLARE @ServerName NVARCHAR(20)
DECLARE @LoginType NVARCHAR(20)
DECLARE @ClientHost NVARCHAR(20)
DECLARE @IP NVARCHAR(20)
DECLARE @STAT INT = 0
DECLARE @IPint BIGINT=0
SET @Data = EVENTDATA()
SET @HostName = HOST_NAME()
SET @PostTime = @Data.value('(/EVENT_INSTANCE/PostTime)[1]', 'NVARCHAR(24)')
SET @Spid = @Data.value('(/EVENT_INSTANCE/SPID)[1]', 'nvarchar(6)')
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 @IP = @ClientHost
IF @ClientHost<>'<local machine>'
begin
IF @LoginName in( SELECT loginName from login_create)
BEGIN
INSERT INTO [Login_Monitor]
VALUES(@SPID, @posttime,@ServerName,@hostname, @loginname,@LoginType,@IP,@STAT)
END
END
END
經過一定週期的監控,確定所有賬戶已經替換後,接下來可以禁用原賬戶了。
禁用原賬戶
--生成禁用登陸帳戶腳本
SELECT 'ALTER LOGIN '+QUOTENAME(loginName,']') +'DISABLE'
FROM login_create
生成腳本後直接執行即可。
清理禁用的舊賬戶
通過觀察,沒有賬戶有報錯後,就可以清理舊的停用賬戶了。因爲刪除登陸賬戶時,沒有辦法直接關聯刪除其對應在各數據庫中的用戶,下面使用循環將停用舊登陸賬戶及對應各數據庫中的用戶刪除,腳本如下:
--刪除停用的帳戶,及各數據庫中對應的用戶
DECLARE @user AS VARCHAR(200)
DECLARE @databaseName AS VARCHAR(200)
DECLARE @sql AS VARCHAR(800)
--循環刪除停用的用戶
DECLARE curUsers CURSOR
FOR
SELECT QUOTENAME(loginName, ']')
FROM login_create
OPEN curUsers
FETCH NEXT FROM curUsers INTO @user
WHILE @@FETCH_STATUS = 0
BEGIN
DECLARE databaseNameCur CURSOR
FOR
SELECT name
FROM sys.databases
WHERE state = 0
OPEN databaseNameCur
FETCH NEXT FROM databaseNameCur INTO @databaseName
WHILE @@FETCH_STATUS = 0
BEGIN
SET @sql = 'use ' + @databaseName + ';'
SET @sql =@sql + 'drop user ' + @user
BEGIN TRY
EXEC(@sql)
PRINT @sql
END TRY
BEGIN CATCH
PRINT '數據庫' + @databaseName + '中不存在用戶' + @user
END CATCH
FETCH NEXT FROM databaseNameCur INTO @databaseName
END
CLOSE databaseNameCur
DEALLOCATE databaseNameCur
USE master
SET @sql = 'drop login ' + @user
PRINT @sql
EXEC(@sql)
FETCH NEXT FROM curUsers INTO @user
END
CLOSE curUsers
DEALLOCATE curUsers