管理触发器安全性

适用于: SQL Server Azure SQL 数据库 Azure SQL 托管实例

默认情况下,在调用触发器的用户的上下文中执行 DML 和 DDL 触发器。 触发器的调用方是执行使触发器运行的语句的用户。 例如,如果用户 Mary 执行可以使 DML 触发器 DML_trigMary 运行的 DELETE 语句,则 DML_trigMary 中的代码将在 Mary 的用户特权上下文中执行。 希望向数据库或服务器实例中引入恶意代码的用户可以使用此默认行为。 例如,用户 JohnDoe 创建以下 DDL 触发器:

CREATE TRIGGER DDL_trigJohnDoe
ON DATABASE
FOR ALTER_TABLE
AS
SET NOCOUNT ON;

BEGIN TRY
  EXEC(N'
    USE [master];
    GRANT CONTROL SERVER TO [JohnDoe];
');
END TRY
BEGIN CATCH
  DECLARE @DoNothing INT;
END CATCH;
GO

此触发器的含义是:在有权执行 GRANT CONTROL SERVER 语句的用户(如 sysadmin 固定服务器角色的成员)执行 ALTER TABLE 语句时,为 JohnDoe 授予 CONTROL SERVER 权限。 换言之,虽然 JohnDoe 不能向自己授予 CONTROL SERVER 权限,但他们启用了授予自己此权限的触发器代码以在升级特权下执行。 对于此类安全隐患,DML 和 DDL 触发器都处于打开状态。

触发器安全最佳方法

可以采取下列措施阻止触发器代码在升级特权下执行:

  • 注意数据库和服务器实例中存在的 DML 和 DDL 触发器,方法是查询 sys.triggerssys.server_triggers 目录视图。 下面的查询将返回当前数据库中的所有 DML 触发器和数据库级别的 DDL 触发器,以及服务器实例中所有服务器级别的 DDL 触发器:

    SELECT type, name, parent_class_desc FROM sys.triggers
    UNION ALL
    SELECT type, name, parent_class_desc FROM sys.server_triggers;
    

    注意

    sys.triggers 适用于 Azure SQL 数据库,除非你使用的是 Azure SQL 托管实例。

  • 请注意数据库中是否存在 DML 和 DDL 触发器,具体方法是查询 sys.triggers 目录视图。 下面的查询返回当前数据库中的所有 DML 和数据库级别 DDL 触发器:

    SELECT type, name, parent_class_desc FROM sys.triggers;
    
  • 使用 DISABLE TRIGGER 禁用在升级特权下执行时可能会损害数据库或服务器完整性的触发器。 下面的语句可以禁用当前数据库中所有数据库级别的 DDL 触发器:

    DISABLE TRIGGER ALL ON DATABASE;
    

    下面的语句可以禁用服务器实例中所有服务器级别的 DDL 触发器:

    DISABLE TRIGGER ALL ON ALL SERVER;
    

    下面的语句可以禁用当前数据库中的所有 DML 触发器:

    DECLARE @schema_name sysname, @trigger_name sysname, @object_name sysname;
    DECLARE @sql nvarchar(max);
    DECLARE trig_cur CURSOR FORWARD_ONLY READ_ONLY FOR
        SELECT SCHEMA_NAME(schema_id) AS schema_name,
            name AS trigger_name,
            OBJECT_NAME(parent_object_id) AS object_name
        FROM sys.objects WHERE type IN ('TR', 'TA');
    
    OPEN trig_cur;
    FETCH NEXT FROM trig_cur INTO @schema_name, @trigger_name, @object_name;
    
    WHILE @@FETCH_STATUS = 0
    BEGIN
        SELECT @sql = N'DISABLE TRIGGER ' + QUOTENAME(@schema_name) + N'.'
            + QUOTENAME(@trigger_name)
            + N' ON ' + QUOTENAME(@schema_name) + N'.'
            + QUOTENAME(@object_name) + N'; ';
        EXEC (@sql);
        FETCH NEXT FROM trig_cur INTO @schema_name, @trigger_name, @object_name;
    END;
    GO
    
    -- Verify triggers are disabled. Should return an empty result set.
    SELECT * FROM sys.triggers WHERE is_disabled = 0;
    GO
    
    CLOSE trig_cur;
    DEALLOCATE trig_cur;