【转载】原文地址:https://www.baidu.com/link?url=N-SM28ge21TTYky79dYk8otsjKgYCIpy-0RBSvMV25f8KSOsYczhxTOCzeNZhaNV-U6gy-xVhAVcyRwiCTLYG_&wd=&eqid=d5aab7f6000533a4000000035afa3701
1.问题背景:
之前有客户朋友在做数据安全加固项目的时候和我聊到,SQLServer能不能像mysql一样设置类似的IP访问白名单、黑名单,以此来加强数据访问安全。我们知道mysql里有张mysql.user表记录了用户的权限信息,其中host、user字段就是用来限制用户IP访问权限的。
当然SQL Server和Oracle是没有这样的权限表的,换个思路,SQLServer我们可以通过登录触发器来间接实现同样的功能,而oracle可通过sqlnet.ora文件中添加CP.VALIDNODE_CHECKING参数来实现
2.登录触发器
SQLServer 包括三种常规类型的触发器:DML TRIGGER、DDL TRIGGER和LOGONTRIGGER。DML触发器是比较常使用的,它主要针对表或视图中的数据修改(INSERT、UPDATE和DELETE等)在激发任何有效的事件时,将会激发这些触发器,而无论是否会影响任何表行;DDL触发器用于响应各种数据定义语言 (DDL) 事件。 这些事件主要对应于Transact-SQL CREATE、ALTER 和 DROP 语句,以及执行类似 DDL操作的某些系统存储过程。 而登录触发器在遇到 LOGON 事件时触发,LOGON 事件是在建立用户会话时引发的,也是我们此文将要提及的触发器。
3.限制登录条件
比如说
1)限制某个login(比如test)只能在本机或指定IP登录
2)限制服务器角色(比如sysadmin)只能在本机或指定IP登录
3)限制某个login(比如testtime)只能在某个时间段登录同时记录非法登录日志
4.实现过程
1)限制某个login(比如test)只能在本机或指定IP登录
当检测到登录账号为test时,则会限制该账号只能在本机,或IP地址为192.168.0.108或192.168.0.109上登录。此例涉及到EVENTDATA()函数,有兴趣可以到msdn上查看其使用方法。
代码:
1 --创建test登录账号 2 3 CREATE LOGIN test WITH PASSWORD = 'test' 4 5 GO 6 7 --创建登录触发器 8 9 ALTER TRIGGER [restrict_login] 10 --控制 数据库引擎使用sa用户帐户来验证对模块引用的对象的权限,是借用,仅在此存储过程中有用 11 ON ALL SERVER WITH EXECUTE AS 'sa' 12 13 FOR LOGON 14 15 AS 16 17 BEGIN 18 --返回连接到 SQL Server 实例的登录名,并校验 19 IF ORIGINAL_LOGIN()= 'test' 20 21 AND 22 --返回有关服务器或数据库事件的信息。校验登陆事件的IP是否为指定IP 23 (SELECT EVENTDATA().value('(/EVENT_INSTANCE/ClientHost)[1]', 'NVARCHAR(15)')) 24 25 NOT IN('','192.168.0.108','192.168.0.109') 26 --在隐式或显式调用 EVENTDATA 的事务提交或回滚之后,EVENTDATA 所返回的数据将无效。 27 ROLLBACK; 28 29 END;
若test账号在除'','192.168.0.108','192.168.0.109'之外的服务器上登录时,则会抛出如下错误
但是在本机上使用test账号则能正常登录,使用以下代码查看登录情况:
SELECT a.[session_id],a.[login_time],a.[host_name], a.[original_login_name],b.[client_net_address] FROM MASTER.sys.dm_exec_sessions a INNER JOIN MASTER.sys.dm_exec_connections b ON a.session_id=b.session_id
2)限制服务器角色(比如sysadmin)只能在本机或指定IP登录
这里我们效仿mysql里的user表,设置了一个IP表来限制访问IP。
当检测到登录login为sysadmin服务器角色(这个角色可以设为diskadmin,dbcreator等9种服务器角色中的任一种),且登录ip地址不在dbo.connectip定义的有效ip地址内,则拒绝登录访问
代码:
1 --创建IP过滤表 2 3 USE db22 4 5 GO 6 7 CREATE TABLE dbo.connectip ( 8 9 IP NVARCHAR(20), 10 11 CONSTRAINT PK_IP PRIMARY KEY CLUSTERED(IP) 12 13 ); 14 15 GO 16 17 18 19 --插入白名单IP 20 21 USE db22 22 23 GO 24 25 INSERT INTO dbo.connectip(IP) VALUES(''); 26 27 INSERT INTO dbo.connectip(IP) VALUES('192.168.0.108'); 28 29 30 31 --创建登录触发器 32 33 34 35 CREATE TRIGGER [restrict_login_check] 36 37 ON ALL SERVER 38 39 FOR LOGON 40 41 AS 42 43 BEGIN 44 --指示 SQL Server 登录名是否为指定服务器角色的成员 45 IF IS_SRVROLEMEMBER('sysadmin') = 1 46 47 BEGIN 48 49 DECLARE @IP NVARCHAR(20); 50 51 SET @IP = (SELECT EVENTDATA().value('(/EVENT_INSTANCE/ClientHost)[1]', 'NVARCHAR(20)')); 52 53 IF NOT EXISTS(SELECT IP FROM [db22].dbo.connectip WHERE IP = @IP) 54 55 ROLLBACK; 56 57 END; 58 59 END;
当我们从ip地址为192.168.0.108(不在有效ip内)服务器上以sa(sysadmin角色)用户远程登录的话,会抛出如下错误
但是我们却可以以不受限的tete账户从192.168.0.106来登录
如果想把这个表内的ip都加密,结果发现不一致导致登录失败了,怎么都进不来,怎么办?按照以下步骤来:
1.打开Sql Server Manage Studio,不要登录(反正是会失败),去菜单:“文件”--“新建”--“使用当前连接的查询”,在打开的登录窗口中,服务器名称输入:ADMIN:127.0.0.1
,账号sa + 原密码,,点击登录,成功
2.在打开的查询界面执行:drop trigger tr_logon_CheckIP on all server 执行成功,就删除掉这个触发器了
3.重新登录sa,发现可以进去了
3)限制某个login(比如testtime)只能在某个时间段登录,同时记录非法登录信息
假设一个场景,我们规定某些用户只能晚上固定某个时间段登录服务器进行一些报表拉取操作,而白天不允许其登录,那么此例可以帮助我们实现而且能达到监控的目的。
代码:
1 --创建登录账号 2 3 CREATE LOGIN testnight WITH PASSWORD = 'testnight' 4 5 GO 6 7 8 9 --创建拦截日志表 10 11 USE db22 12 13 GO 14 15 CREATE TABLE dbo.login_record ( 16 17 [Id] INT IDENTITY(1,1), 18 19 [session_id] SMALLINT, 20 21 [login_time] DATETIME, 22 23 [host_name] NVARCHAR(128), 24 25 [original_login_name] NVARCHAR(128), 26 27 [client_net_address] VARCHAR(48), 28 29 CONSTRAINT PK_id PRIMARY KEY CLUSTERED(Id) 30 31 ); 32 33 34 35 --创建登录触发器 36 37 38 39 ALTER TRIGGER [restrict_login_record] 40 41 ON ALL SERVER WITH EXECUTE AS 'sa' 42 43 FOR LOGON 44 45 AS 46 47 BEGIN 48 49 IF ORIGINAL_LOGIN()='testnight' AND 50 51 DATEPART(hh,GETDATE()) BETWEEN 9 AND 18 52 53 BEGIN 54 55 ROLLBACK; 56 57 INSERT INTO [db22].[dbo].[login_record] 58 59 ([session_id] 60 61 ,[login_time] 62 63 ,[host_name] 64 65 ,[original_login_name] 66 67 ,[client_net_address]) 68 69 SELECT 70 71 a.[session_id],a.[login_time],a.[host_name], 72 73 a.[original_login_name],b.[client_net_address] 74 75 FROM MASTER.sys.dm_exec_sessions a 76 77 INNER JOIN MASTER.sys.dm_exec_connections b 78 79 ON a.session_id=b.session_id 80 81 WHERE a.session_id = @@SPID 82 83 END; 84 85 END; 86 87
此例中,如果testnight用户在白天9:00-18:00任意时间登录,则会抛出如下登录错误
同时会在拦截日志表dbo.login_record中有如下记录:
甚至我们可以模仿mysql里的mysql.user表建立一个含host,user字段的登录白名单表,用来保存用户与IP对应关系,这样就可以对所有用户进行管控了,其实它的实现也很简单,主要是一个取IP的方法:EVENTDATA().value('(/EVENT_INSTANCE/ClientHost)[1]','NVARCHAR(15)'),然后与白名单表里的IP和user做比对,若白名单表里没有对应项,则ROLLBACK并记录登录信息到log表即可,这里就不赘述了,有兴趣的朋友可以试试。