SQL 账号权限须遵循最小权限原则:新建账号限定 IP 范围,按需授予库表级权限,禁用高危权限,DEFINER 设为低权限账号,拆分读写账号,严格配置 pg_hba.conf,并定期审计回收临时授权。

SQL 账号权限别乱给,GRANT 前先想清楚能查哪些表
多数数据库被拖库,不是因为密码弱,而是账号权限过大。比如一个只负责写日志的后台服务,却拥有 SELECT 全库、甚至 DROP TABLE 权限——攻击者一旦拿下这个服务,等于直接拿到数据库后门。
实操建议:
- 新建账号时,用
CREATE USER明确指定主机范围(如'app'@'10.20.30.%'),禁止'app'@'%'这种全网可连写法 - 授权不用
GRANT ALL ON *.*,而是按需精确到库 + 表,例如:GRANT SELECT, INSERT ON myapp.logs TO 'logger'@'10.20.30.%' - 业务账号一律禁用
SUPER、FILE、PROCESS等高危权限,MySQL 8.0+ 还要检查是否误授了BACKUP_ADMIN - PostgreSQL 同理,避免直接
GRANT ALL PRIVILEGES,改用GRANT SELECT ON TABLE users TO app_user
MySQL 的 DEFINER 函数 / 视图是隐形权限放大器
函数或视图设了 DEFINER = 'root'@'%',哪怕调用者只是普通账号,执行时也会以 root 权限运行——这等于绕过最小权限原则。
常见错误现象:运维发现某个低权限账号竟能删配置表,追查发现是它调用了带 DEFINER 的存储过程。
实操建议:
- 所有自定义函数、存储过程、视图,
DEFINER必须设为当前业务账号本身,或一个专用的低权限账号(如'definer_app'@'localhost') - 建函数时显式声明:
CREATE DEFINER = 'app_rw'@'10.20.30.%' FUNCTION …… - 定期扫描:
SELECT name, definer FROM mysql.proc WHERE definer NOT LIKE '%app%';找出可疑DEFINER
连接池和 ORM 默认用的账号,往往就是最危险的那个
Spring Boot 的 application.yml 里配的 spring.datasource.username,Django 的 DATABASES['default']['USER'],这些账号通常被整个应用共享。一旦泄露或被注入,影响面极大。
使用场景:API 接口被 SQL 注入 → 攻击者用该连接执行任意语句 → 因为账号权限高,直接导出用户表。
实操建议:
- 拆分账号:读操作用
app_reader(只含SELECT),写操作用app_writer(含INSERT/UPDATE,但禁DELETE和 DDL),管理后台单独配admin_ui - ORM 层不拼接 SQL,但更要确认底层连接用的是哪个账号——有些框架会悄悄用 root 初始化元数据表
- 数据库代理层(如 ProxySQL、MaxScale)可做账号路由,让不同路径的请求自动切换底层账号,减少应用侧硬编码
PostgreSQL 的 pg_hba.conf 不是摆设,IP+ 用户 + 数据库三者必须严格匹配
很多人以为只要账号权限设对了就安全,忽略了连接入口控制。PostgreSQL 的 pg_hba.conf 若写成 host all all 0.0.0.0/0 md5,等于把锁打开,钥匙还随便放。
性能 / 兼容性影响:规则顺序很重要,靠前的规则优先匹配;写错掩码(如把 192.168.1.0/24 写成 192.168.1.0/16)会导致非预期放行。
实操建议:
- 每条规则明确限定三要素:
host <database> <user> <CIDR> <auth-method>,例如:host appdb app_writer 10.20.30.0/24 scram-sha-256 - 禁止
local all all trust,本地连接也应走scram-sha-256或peer - 修改后必须
pg_ctl reload或SELECT pg_reload_conf();,否则配置不生效
最小权限不是设一次就完事的事。应用迭代时新增表、新接口、新定时任务,很容易顺手加个 GRANT ALL —— 这类临时授权,三个月后基本没人记得回收。定期用脚本比对 information_schema.role_table_grants 和当前业务需求清单,比等审计报告更管用。