彻底禁用需同时回收event/trigger权限并关闭event_scheduler:revoke event,trigger on db.* from 'u'@'h'; set global event_scheduler = off;旧对象须手动drop,因权限回收不影响已存在event/trigger运行。
mysql 8.0+ 中如何彻底禁用 event 和 trigger 权限
直接结论:仅靠 drop 或 revoke 已创建对象没用,必须从权限层阻断用户创建能力;否则攻击者可新建 event/trigger 实现后门持久化。
关键点在于:MySQL 的 EVENT 和 TRIGGER 权限是独立授予的,不依赖 CREATE 或 ALTER 等通用权限。即使用户只有 SELECT,只要被显式授予了 EVENT,就能建定时任务。
- 检查当前用户是否拥有相关权限:
SHOW GRANTS FOR 'username'@'host';
- 彻底回收(注意:需在对应数据库上下文中执行):
REVOKE EVENT ON `db_name`.* FROM 'username'@'host';<br>REVOKE TRIGGER ON `db_name`.* FROM 'username'@'host';
- 若要全局禁止(如运维账号外的所有普通账号),对
*授予时就不要包含这两项:GRANT SELECT, INSERT, UPDATE ON `app_db`.* TO 'app_user'@'%'; -- 不加 EVENT/TRIGGER
为什么 revoke 后还能看到旧 Event/Trigger?
因为 REVOKE EVENT 只阻止新建和修改,不影响已存在的对象运行或查询。旧 Event 仍会触发,旧 Trigger 仍会在 INSERT/UPDATE 时执行——这是权限模型设计使然,不是 bug。
- 确认残留风险:
SELECT * FROM information_schema.EVENTS WHERE EVENT_SCHEMA = 'db_name';<br>SELECT * FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA = 'db_name';
- 清理动作必须手动:
DROP EVENT IF EXISTS `backdoor_event`;<br>DROP TRIGGER IF EXISTS `log_insert_trigger`;
- 特别注意:Event 默认可能设为
ENABLE,且能调用SLEEP()、LOAD_FILE()等高危函数,务必逐个检查EVENT_DEFINITION字段内容
低权限账号被提权的常见绕过路径
即使你 revoke 了 EVENT/TRIGGER,攻击者仍可能通过其他方式间接达成类似效果,这些才是真实渗透中更隐蔽的入口。
-
DEFINER机制陷阱:若用户能创建存储过程(CREATE ROUTINE),且该过程以高权限账号(如root@localhost)定义,调用时将继承其权限,等效于提权执行任意 SQL - UDF(自定义函数):若 MySQL 允许加载动态库(
secure_file_priv未禁用且目录可写),攻击者可注入恶意 UDF,绕过所有 SQL 权限限制 - 日志类表写入:部分版本允许向
mysql.general_log或mysql.slow_log表插入数据(如果启用了对应日志并设为表存储),配合 SELECT INTO OUTFILE 可间接落盘
配置层面真正有效的防御组合
单靠权限回收不够,必须配合服务端配置收紧,否则权限只是纸面约束。
- 关闭非必要功能:
SET GLOBAL event_scheduler = OFF; -- 彻底停用事件调度器,所有 Event 停摆
(重启后失效,需写入my.cnf的[mysqld]段) - 加固配置项:
secure_file_priv = /tmp/ -- 限定文件操作路径,避免 LOAD DATA 写入敏感位置<br>sql_mode = STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION -- 防止隐式权限提升
- 最小化账号权限范围:避免使用
ON *.*授权;生产库应按业务模块拆分数据库,并为每个应用账号限定到具体库名,而非通配符
最常被忽略的是:event_scheduler 默认是 ON,且没有对应权限控制开关——只要用户有 EVENT 权限,就能开启自己的调度器实例。所以“禁用权限”和“关闭调度器”必须同时做,缺一不可。










