数据库账号权限是SQL注入最后一道实打实的止损线:MySQL需列级授权并显式回收宽泛权限,SQL Server须禁用系统库访问,PostgreSQL视图需加CHECK OPTION并防SECURITY DEFINER陷阱。

只靠参数化查询防不住所有注入场景,数据库账号权限才是最后一道实打实的止损线。
MySQL中给应用账号授单表列级SELECT权限
关键不是“能不能查”,而是“只能查哪张表的哪些字段”。全局 SELECT 权限等于把整栋楼门禁卡交出去。
- 先建用户:
CREATE USER 'web_ro'@'192.168.10.%' IDENTIFIED BY 'p4ss_R3ad0nly_2026'; - 只授指定字段:
GRANT SELECT (id, username, email) ON myapp.users TO 'web_ro'@'192.168.10.%';(注意:MySQL 8.0+ 支持列级授权,旧版本需用视图兜底) - 显式回收宽泛权限:
REVOKE SELECT ON *.* FROM 'web_ro'@'192.168.10.%';(否则可能隐式继承) - 验证结果:
SHOW GRANTS FOR 'web_ro'@'192.168.10.%';,确认输出里没有ON *.*或myapp.*
SQL Server里db_datareader角色为什么不够安全
它默认允许查 master、msdb 等系统库——攻击者用 UNION SELECT name FROM master..sysdatabases-- 就能枚举库名。
- 必须显式禁止:
DENY SELECT ON SCHEMA::sys TO app_user; - 禁用高危能力:
DENY CONTROL SERVER TO app_user; - 只加最小读角色:
ALTER ROLE db_datareader ADD MEMBER app_user;(但仅限业务库) - 别信
xp_cmdshell关了就安全——注入成功后第一件事是拖库,不需要提权
PostgreSQL中用视图封装时的CHECK OPTION陷阱
视图不是防火墙,是数据过滤网。不加 WITH CHECK OPTION,INSERT 可能绕过 WHERE 条件写入非法数据。
- 建视图要带检查:
CREATE VIEW public.active_users AS SELECT id, name FROM users WHERE status = 'active' WITH CHECK OPTION; - 撤掉基表权限后,
SELECT * FROM active_users WHERE id = 123 OR 1=1仍可执行,但永远只返回status='active'的行 - 警惕
SECURITY DEFINER函数:若视图里调用了有pg_read_all_data角色的函数,整个视图就变成高危通道 - 验证权限链:
\z public.active_users查 ACL,再SELECT relacl FROM pg_class WHERE relname = 'active_users';
容易被忽略的三个权限盲区
最小权限不是配一次就完事的事。应用迭代时新增的 SQL,往往悄悄要求新权限,而没人去补。
- 定时任务用的账号常被遗忘——比如凌晨跑销量统计的 Python 脚本,可能还连着一个拥有
SELECT所有表的旧账号 - 开发/测试环境照搬生产权限,本地调试时误删测试数据,或掩盖了权限不足导致的 bug
- 第三方 SDK(如老版支付回调组件)会悄悄执行
SHOW TABLES或SELECT @@version,被拒就抛异常,得提前白名单放行元数据查询
真正卡住攻击链的,往往不是最炫的防护技术,而是那个没人记得改、也没人敢动的数据库账号权限配置。











