应用账号应仅授予select/insert等最小必要权限,禁用drop、grant option等高危权限,并限制ip范围、显式收回file/process/super权限,配置secure_file_priv和strict sql_mode以阻断文件读写与类型绕过。

应用账号只给SELECT/INSERT,别碰DROP和GRANT
SQL注入一旦发生,攻击者能执行什么操作,直接取决于数据库账号的权限。如果应用账号拥有DROP、CREATE、GRANT OPTION或对mysql系统库的访问权,一条注入语句就可能清空表、创建高权限用户、甚至读取其他数据库的敏感配置。
实际操作中应严格限制:
- 用
CREATE USER 'app_api'@'10.20.%'建专用账号,密码必须强且定期轮换 - 只授业务必需的权限:
GRANT SELECT, INSERT ON billing_db.invoice TO 'app_api'@'10.20.%' - 显式收回高危权限:
REVOKE FILE, PROCESS, SUPER ON *.* FROM 'app_api'@'10.20.%'(即使没授过,也建议写上) - 绝对避免
GRANT ... ON *.*——新库上线时极易漏掉权限收敛
secure_file_priv设成非空目录,堵死LOAD DATA和INTO OUTFILE
很多SQL注入利用LOAD DATA INFILE或SELECT ... INTO OUTFILE读取服务器文件(如/etc/passwd)或写入Webshell。MySQL默认secure_file_priv为空,等于完全放开路径限制。
必须在my.cnf里明确指定:
-
secure_file_priv = /var/lib/mysql-files/(目录需真实存在且MySQL有读写权限) - 重启mysqld后验证:
SELECT @@secure_file_priv返回值必须是具体路径,不能是NULL或空字符串 - 同时禁用
LOCAL INFILE:SET GLOBAL local_infile = OFF(需SUPER权限),或启动时加--local-infile=0
sql_mode启用STRICT_TRANS_TABLES,防隐式类型转换绕过
宽松模式下,MySQL会把'1' = 1这种混合类型比较自动转为真,导致WHERE id = '$user_input'被绕过——输入1 OR 1=1可能被当成整数1处理,后续逻辑失效。
在my.cnf的[mysqld]段加上:
sql_mode = "STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION"- 重启后检查:
SELECT @@sql_mode确认生效 - 注意:已有应用若依赖宽松行为(比如允许空字符串插入NOT NULL字段),需先测试再上线
audit_log插件比general_log更靠谱,但社区版不自带
general_log开全量日志会严重拖慢性能,且日志无结构、难过滤;而真正的审计需求需要记录谁、何时、从哪台机器、执行了什么语句。
生产环境该用审计插件:
- Percona Server或MariaDB用户可启用
audit_log:安装插件后设audit_log_policy = ALL - MySQL官方企业版才有
audit_log插件,社区版只能靠第三方方案(如pt-query-digest分析slow log) - 关键点:审计日志必须独立存储、限制访问权限,且保留周期要满足合规要求
权限控制不是万能盾牌,但它能让一次成功的SQL注入止步于单张表的SELECT——真正致命的往往是那些被忽略的REVOKE语句和空着的secure_file_priv。











