ddl触发器必须显式启用且作用域正确,否则不生效;须用eventdata()安全解析xml提取对象名,配合trycatch、raiserror≥16级及rollback才能硬拦截;truncate_table无法被ddl触发器捕获,需权限管控或审计替代。

DDL触发器必须显式启用,否则完全不生效
新建的 CREATE TRIGGER 默认是禁用状态,哪怕语法完全正确,它也不会响应任何 DROP TABLE。很多线上事故就源于“建完就以为万事大吉”。
- 检查是否启用:
SELECT is_disabled FROM sys.triggers WHERE name = 'tr_block_drop'—— 返回1表示禁用 - 启用命令必须带作用域:
ENABLE TRIGGER tr_block_drop ON DATABASE(当前库)或ENABLE TRIGGER tr_block_drop ON ALL SERVER(全局,慎用) - 服务器级触发器(
ON ALL SERVER)对用户数据库里的DROP TABLE无效,它只响应CREATE LOGIN、ALTER DATABASE这类跨库事件
提取对象名必须用 EVENTDATA(),OBJECT_NAME() 在 DDL 触发器里返回 NULL
OBJECT_NAME() 在 DDL 触发器中不可靠——表已被删或尚未创建,函数直接返回 NULL。真正可用的是 EVENTDATA() 返回的 XML,但节点名大小写、路径稍错就全失效。
- 正确提取表名:
CAST(EVENTDATA() AS XML).value('(/EVENT_INSTANCE/ObjectName)[1]', 'sysname') - 提取 Schema:
CAST(EVENTDATA() AS XML).value('(/EVENT_INSTANCE/SchemaName)[1]', 'sysname') - 必须用
TRY...CATCH包裹解析逻辑,否则 XML 结构微调(如新版 SQL Server 改字段名)会导致触发器报错中断事务,反而阻断合法运维 - 别写
/ObjectNamee或漏掉[1],XPath 错一个字符,整个判断就跳过
白名单不能只比对登录名,要绑定会话上下文
硬编码 ORIGINAL_LOGIN() IN ('sa', 'dba_admin') 是常见漏洞:所有归档脚本都用同一个账号运行,那这个账号删任何表都畅通无阻。
- 应结合
APP_NAME()、HOST_NAME()或自定义上下文(SET CONTEXT_INFO)做联合判断 - 例如应用层执行清理前先设标识:
SET CONTEXT_INFO 0x5472756E636174654A6F62,触发器中用CONTEXT_INFO()提取校验 - 临时表放行不能靠
LIKE '%temp%',攻击者可建user_temp_drop_me绕过;应严格匹配系统命名规范(如以#或##开头)
RAISERROR 级别必须 ≥16,否则无法中断事务
级别低于 16 的错误(比如 RAISERROR(..., 10, 1))只是警告,事务继续执行,DROP TABLE 仍会成功。
- 必须用
RAISERROR('禁止删除表', 16, 1)或更高(16–25),才能让 SQL Server 中断当前批处理并回滚 - 紧跟
RAISERROR后必须加ROLLBACK,不能依赖错误自动回滚——某些客户端设置可能忽略错误继续执行后续语句 - 别在
PRINT后就结束,PRINT不影响事务流,纯属日志输出
TRUNCATE TABLE 完全不受 DDL 触发器控制,这是 SQL Server 的底层设计限制,不是配置问题。想防它,得换思路:收掉 ALTER 权限、用存储过程封装清理逻辑、或上 SQL Server Audit + 正则拦截。别把精力花在给 FOR DROP_TABLE 加条件上——它根本看不到 TRUNCATE。











