MySQL安全加固实战指南:从裸奔到等保合规的完整实施方案
大家好,我是数据库小学妹 👋
前不久部门过等保测评,整体过了,但MySQL安全这块被扣了分。领导一看都是配置层面的问题,改改参数就行,就把活分给了我。我心想安全不就是设个密码开个权限嘛,结果一查规范才发现,光MySQL就有七个维度要加固,审计老师问的三个问题我一个都答不上来:密码策略配了吗?没有。操作审计开了吗?没有。敏感数据加密了吗?也没有。
当时脸都绿了。回去恶补了一堆等保规范和安全加固文档,对着规范一条一条改,踩了不少坑才把七个维度全部补齐。今天把我这一周的实操经验整理成7道防线,按紧急程度排序,拿来就能用。
第一道:网络隔离——MySQL默认监听所有网卡,这一步很多人忘了
这是最基础的一步,很多人第一步就没做好。我之前也是,装完MySQL直接用默认配置,根本没想过监听地址这回事。
MySQL默认监听0.0.0.0,所有网卡都能连。生产环境只监听内网网卡:
[mysqld]
bind-address = 10.0.1.100
root账户只允许从localhost连,远程管理用普通账号加sudo:
DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1');
FLUSH PRIVILEGES;
MySQL默认安装可能带匿名用户,这是个大隐患,查出来就删:
DELETE FROM mysql.user WHERE User='';
FLUSH PRIVILEGES;
防火墙只放行应用服务器的IP访问3306端口,其他全拒。很多团队觉得内网就安全,不做防火墙限制,内网被渗透的案例真不少。
第二道:密码策略——审计老师第一个查的就是这个
网络隔离关好了,密码这道门也得锁上。说实话,审计老师问的第一个问题就是密码策略,我当时心想"密码不就是设个复杂点的嘛",结果一看规范才发现远不止这么简单。
MySQL自带validate_password插件,能强制密码复杂度,先装上:
INSTALL COMPONENT 'file://component_validate_password';
SET GLOBAL validate_password.policy = MEDIUM;
SET GLOBAL validate_password.length = 12;
MEDIUM策略要求密码至少12位,大小写字母加数字加特殊字符都得有。
密码不能永久有效,90天过期一次,过期前MySQL会提示用户改。再限制密码历史复用,最近5次用过的密码不能再设:
SET GLOBAL default_password_lifetime = 90;
SET GLOBAL password_history = 5;
认证插件也要升级。我之前踩过一个坑,旧项目从5.7升到8.0,客户端驱动没跟着升级,结果连接直接报错。8.0默认用caching_sha2_password,比老的mysql_native_password安全,但你得先确认所有客户端驱动都支持新插件,再切换。
第三道:审计日志——出了事没日志,你连问题出在哪都不知道
出了安全事件没日志,你连问题出在哪都查不到。等保规范里明确要求有操作审计能力,没有日志直接一票否决,MySQL这块也是因为这个被扣了不少分。
MySQL Enterprise版自带audit_log插件,社区版用MariaDB的audit_log插件替代:
-- 社区版安装方式(需下载对应版本的so文件)
INSTALL PLUGIN audit_log SONAME 'audit_log.so';
SET GLOBAL audit_log_policy = 'ALL';
全量审计对性能有影响,一般在5%-10%左右,具体看业务的QPS。性能敏感的话只记DDL、权限变更、批量删除就行:
SET GLOBAL audit_log_policy = 'LOGINS,QUERIES_DDL,QUERIES_DCL';
general_log记录所有SQL,排查问题很方便,但性能影响大,生产环境别常开。调试时临时开一下,排完关掉。
SET GLOBAL general_log = 'ON';
-- 排查完立即关掉
SET GLOBAL general_log = 'OFF';
第四道:数据加密——备份文件泄露了也不怕
加密分传输层、存储层、备份层三块,等保老师会一层一层查。
传输层强制SSL,防止数据在网络上被嗅探:
[mysqld]
require_secure_transport = ON
ssl-ca = /path/to/ca.pem
ssl-cert = /path/to/server-cert.pem
ssl-key = /path/to/server-key.pem
存储层用TDE(透明数据加密),应用不用改一行代码,数据存盘自动加密,读取的时候自动解:
-- 8.0开启TDE
ALTER TABLESPACE mysql ENCRYPTION='Y';
-- 新建表默认加密
SET GLOBAL default_table_encryption=ON;
TDE对性能影响大概3%-5%,因为每次读写都要走加解密,I/O密集型场景会更明显。我之前在测试环境对比过,纯查询场景影响小,大批量写入场景影响大一些。备份也要加密,xtrabackup支持加密备份,备份文件泄露了也不怕。备份文件和密钥要分开存放,备份放OSS,密钥放KMS,不能放在同一个地方。密钥丢了数据就真没了,这个比备份本身重要得多。
第五道:SQL注入纵深防御——光靠应用层过滤远远不够
之前写过SQL注入防护的文章,这里补充数据库侧的加固。
关闭local_infile,防止攻击者用LOAD DATA LOCAL INFILE读取服务器文件。我见过一个案例,攻击者通过SQL注入配合local_infile,直接把/etc/passwd读出来了:
[mysqld]
local_infile = OFF
用存储过程做权限收敛。应用账号不直接授表的增删改查,只授存储过程执行权限,有SQL注入也没用,攻击者能做的事情很有限。我第一次听到这个思路的时候觉得多此一举,后来真遇到一次SQL注入事件,攻击者拿到了应用账号,但因为只有存储过程权限,什么都查不了,最后只触发了审计告警就止住了:
CREATE PROCEDURE sp_get_user(IN p_user_id INT)
BEGIN
SELECT id, name, phone FROM users WHERE id = p_user_id;
END;
GRANT EXECUTE ON PROCEDURE sp_get_user TO 'app_user'@'%';
第六道:敏感数据脱敏——手机号身份证明文往外发,等保过不了的
手机号、身份证号、银行卡号这些字段,等保里属于个人敏感信息,不能让人看到明文。我之前导数据给开发测试,直接把原始表权限开出去了,被安全同事提醒才知道要先脱敏。脱敏分两种:静态脱敏是把数据改了再导出,适合给测试环境用;动态脱敏是查询时实时打码,原始数据不动,适合生产环境。
导出数据给开发测试之前先做脱敏处理:
-- 手机号中间四位打星
SELECT CONCAT(LEFT(phone, 3), '****', RIGHT(phone, 4)) AS phone_masked
FROM users;
-- 身份证号保留前3后4
SELECT CONCAT(LEFT(id_card, 3), '***********', RIGHT(id_card, 4)) AS id_masked
FROM users;
动态脱敏用视图实现,不同角色看到不同级别的数据。生产环境建议所有对外提供的查询接口都走视图,不要直接暴露原始表:
-- 普通查询走脱敏视图
CREATE VIEW v_users_masked AS
SELECT id, name,
CONCAT(LEFT(phone,3), '****', RIGHT(phone,4)) AS phone
FROM users;
-- 只有授权角色能查原始表
GRANT SELECT ON users TO 'auditor'@'%';
GRANT SELECT ON v_users_masked TO 'report_user'@'%';
第七道:安全基线巡检——防线建好了不查,迟早会松
防线建好了还得定期查有没有松动。我加固完之后有一阵没管,结果同事新建了个测试账号,权限给得很大,一直没人发现,从那以后我每周跑一次巡检。我写了个巡检脚本,纯SQL实现,MySQL里直接跑:
-- 检查项1:空密码用户
SELECT User, Host FROM mysql.user WHERE authentication_string='';
-- 检查项2:拥有所有权限的用户
SELECT User, Host FROM mysql.user WHERE Super_priv='Y' AND User != 'root';
-- 检查项3:远程root登录
SELECT User, Host FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost','127.0.0.1');
-- 检查项4:密码过期策略
SHOW VARIABLES LIKE 'default_password_lifetime';
-- 检查项5:SSL是否启用
SHOW VARIABLES LIKE 'have_ssl';
-- 检查项6:审计插件是否加载
SELECT PLUGIN_NAME FROM information_schema.PLUGINS WHERE PLUGIN_NAME LIKE '%audit%';
-- 检查项7:binlog是否被误删或权限过大
SHOW VARIABLES LIKE 'log_bin';
SHOW VARIABLES LIKE 'binlog_expire_logs_seconds';
-- 检查项8:用户密码是否使用旧认证插件
SELECT User, Host, plugin FROM mysql.user WHERE plugin='mysql_native_password';
前6项是基础检查,第7项防binlog泄露(里面记录了所有数据变更),第8项防弱认证。我加了第7项是因为有一次排查问题发现binlog文件权限是777,任何用户都能读。
放到定时任务里每周跑一次,结果异常就告警。我用的方式是SQL脚本输出到文件,配合监控系统检测输出内容。
安全加固实施路线图
按紧急程度分三步走。
1小时内搞定:删匿名用户、关远程root登录、绑监听地址。改完重启MySQL就行,风险最低,建议先做。
1天内搞定:启密码复杂度策略、配SSL、关local_infile、开审计日志。这些需要重启服务,选业务低峰期操作。
1周内搞定:TDE加密、备份加密、脱敏视图改造、巡检脚本部署。影响面大,一定先在测试环境跑通,确认没问题再上生产。
注意事项
TDE开之前一定要压测。我在读写频繁的订单库上开了TDE,QPS掉了大概5%,老板差点没让我过试用期。后来在测试环境压测确认了影响范围,折中方案是只对敏感表开加密。
audit_log默认不限制大小,不做日志轮转磁盘很快就满了。配合logrotate做日志切割,保留30天够用。
脱敏视图别套太多层。有人为了安全套了三四层,查询性能直接拉胯。两层够了,一层做权限控制一层做脱敏,超过两层就该重新设计。
写在最后
这7道防线覆盖了MySQL安全加固最核心的部分,不用一口气全做完,按实施路线图分三步走就行,先把最紧急的搞定。你们公司过等保的时候,MySQL安全加固这块卡在哪了?欢迎在评论区聊聊,说不定你的坑我也踩过 👇
我是数据库小学妹,咱们下篇见 👋
- 点赞
- 收藏
- 关注作者
评论(0)