只给 SELECT 权限是否真能保证只读?
不能直接只授
SELECT就完事。MySQL 的只读控制有两层:显式权限(GRANT)和隐式行为(如事务、临时表、存储过程调用)。即使用户只有
SELECT,若拥有
EXECUTE或能访问含
SELECT ... FOR UPDATE的视图,仍可能间接触发写操作或锁表。
实操建议:
显式回收所有非必要权限:REVOKE INSERT, UPDATE, DELETE, DROP, CREATE, ALTER, INDEX, TRIGGER, CREATE VIEW, SHOW VIEW, EXECUTE ON *.* FROM 'user'@'%';避免授予
USAGE以外的全局权限(
ON *.*),必须按库粒度授权,例如
ON `app_db`.*确认用户无
PROCESS或
SUPER权限——它们可绕过部分只读限制
如何防止用户通过视图或存储过程越权写入?
MySQL 视图默认以定义者(DEFINER)权限执行,但若视图定义者是高权限账号,且用户有
EXECUTE或
SELECT视图权限,就可能借视图执行底层写操作(比如视图基于
INSERT ... SELECT的 MERGE 算法,或包含触发器逻辑)。
安全做法:
创建视图时强制指定低权限DEFINER = 'readonly_user'@'%',并确保该用户本身无写权限 禁用
SQL SECURITY DEFINER,改用
SQL SECURITY INVOKER,让视图执行时校验调用者权限 对生产环境视图,用
SHOW CREATE VIEW view_name检查是否含
FOR UPDATE、子查询含
INSERT/UPDATE或依赖函数(如
UUID_SHORT()可能触发系统表写入)
SET SESSION TRANSACTION READ ONLY 有用吗?
这个语句只对**当前会话**生效,且仅阻止显式 DML(
INSERT/
UPDATE/
DELETE),不防 DDL、临时表、用户变量赋值,也不影响其他会话。它无法替代权限控制,仅适合应用层主动声明语义,不能作为安全边界。
典型误用场景:
在连接池初始化 SQL 中执行SET SESSION TRANSACTION READ ONLY—— 多数连接池(如 HikariCP)不保证每次获取连接后重置会话状态 依赖它防御恶意 SQL 注入 —— 攻击者可先执行
SET SESSION TRANSACTION READ WRITE再写入 未配合
read_only=ON全局参数 —— 单纯会话级设置对复制线程、root 用户无效
最小权限组合推荐(生产可用)
真正最小、可控、可审计的只读用户权限不是“只给 SELECT”,而是明确限定对象 + 显式禁止副作用操作。以下为经过验证的 GRANT 模板:
CREATE USER 'report_user'@'192.168.1.%' IDENTIFIED BY 'strong_pass'; GRANT SELECT ON `sales_db`.* TO 'report_user'@'192.168.1.%'; GRANT SELECT ON `log_db`.`daily_summary` TO 'report_user'@'192.168.1.%'; GRANT SHOW VIEW ON `sales_db`.* TO 'report_user'@'192.168.1.%'; FLUSH PRIVILEGES;
关键点:
不授USAGE以外的全局权限(
ON *.*) 不授
EXECUTE,除非明确需要调用特定只读函数(如
MD5())且已验证函数无副作用 若需
SELECT系统表(如
information_schema.TABLES),单独授权,避免
SELECT ON *.*定期用
SELECT * FROM mysql.tables_priv WHERE User='report_user';核对实际权限表,防止 GRANT 覆盖遗漏
最易被忽略的是:MySQL 8.0+ 的角色(ROLE)机制虽方便批量管理,但
SET ROLE可动态切换权限,若未禁用(
SET PERSIST role_restrictions = ON),仍存在运行时提权风险。
