最小权限不能阻止SQL注入发生,但能限制其危害;攻击者只能执行数据库账号被授予的权限,如无SELECT权限则UNION查询直接报错,因此必须与参数化查询配合使用。

SELECT current_user(); 返回的不是你期望的账号,而是攻击者能用的那个——这就是最小权限原则没落地时的真实后果。它不能阻止SQL注入发生,但能决定注入之后还能干成什么事。
为什么最小权限对SQL注入有实际约束力
SQL注入本身是代码层漏洞,靠参数化查询才能根治;但最小权限是最后一道闸门:哪怕攻击者拼出了 UNION SELECT password FROM users,如果数据库账号连 users 表的 SELECT 权限都没有,这条语句直接报错 ERROR 1142 (42501): SELECT command denied。
关键点在于:权限控制发生在数据库执行阶段,不依赖应用逻辑,也不受输入过滤是否漏掉某个字符的影响。
- 攻击者拿到的不是“你的代码”,而是“你数据库账号能干的事”
- 高权限账号(如
root、sa)一旦被注入利用,等于交出服务器控制权 - 低权限账号即使被完全绕过认证,也只能查一两个视图、写一个日志表
如何为Web应用创建真正受限的数据库账号
别用开发环境里那个万能 admin 账号上线。生产环境必须单独建账号,并只授必要权限。
以 MySQL 为例:
- 建账号:
CREATE USER 'app_rw'@'%' IDENTIFIED BY 'strong_password_2026'; - 只给特定库的读写:
GRANT SELECT, INSERT, UPDATE ON myapp_db.users TO 'app_rw'@'%'; - 禁止跨库操作:
REVOKE SUPER, FILE, PROCESS ON *.* FROM 'app_rw'@'%'; - 禁用危险命令:
REVOKE CREATE ROUTINE, ALTER ROUTINE, EXECUTE ON *.* FROM 'app_rw'@'%'; - 立刻生效:
FLUSH PRIVILEGES;
PostgreSQL 类似,用 CREATE ROLE app_ro WITH LOGIN PASSWORD '...'; + GRANT SELECT ON TABLE orders TO app_ro;,但要注意默认 schema 权限需显式收回。
哪些权限最容易被忽略却最危险
很多团队给了 SELECT 和 INSERT 就以为安全了,但以下权限一旦开放,注入后危害陡增:
-
UNION查询依赖SELECT权限,但若账号能访问多个表(比如同时有users和credit_cards的SELECT),UNION SELECT就能横跳取数据 -
EXECUTE权限允许调用存储过程——而很多老系统把敏感逻辑写在 SP 里,等同于开了后门 -
LOAD DATA INFILE(MySQL)或COPY FROM PROGRAM(PostgreSQL)可直接读取服务器文件,配合注入等于远程代码执行 -
SHOW DATABASES或information_schema读取权限,会让盲注效率翻倍
检查方式很简单:SHOW GRANTS FOR 'app_rw'@'%';,逐条核对,不是“没开就默认安全”,而是“没明确开就不该有”。
最小权限和参数化查询必须一起用
有人觉得:“我都限制权限了,拼接 SQL 也没事吧?” 错。权限收缩只是降低损失,不是替代修复。
- 权限再低,只要允许
INSERT,攻击者就能往日志表塞恶意 payload,后续触发 XSS 或 SSRF - 权限再严,只要没禁用
SELECT,布尔盲注仍可能通过IF(1=1, SLEEP(1), 0)慢慢拖出数据 - ORM 框架(如 Django ORM、MyBatis
#{})默认参数化,但手写原生 SQL 时仍可能漏掉,此时权限就是兜底线
真正有效的防线是双保险:代码层用 PreparedStatement 或 pg_query_params() 拦住绝大多数注入,数据库层用最小权限卡死残余风险。两者缺一不可,且后者容易被当成“运维的事”而拖到上线前最后一刻才配——那已经晚了。

















