数据库是网站和应用的命根子,也是入侵者的头号目标。SQL 注入拿不到管理员密码时,攻击者会转而瞄数据库端口、弱口令、以及过度授权的账号。
这篇讲数据库(以 MySQL/MariaDB 为主)的安全加固:账号权限最小化、网络访问控制、数据加密、审计日志。照着做完,数据库这块基本固若金汤。
需要云服务器可以看看财富云计算:NAT 型无独立公网 IP,价格比独立 IP 机型省一半以上,一键开服面板,主流游戏快速部署。
一、先做安全自查
在服务器上跑一遍,看看有多少问题:
# 1. 有没有匿名账号/空密码账号
mysql -u root -p -e "SELECT user, host, authentication_string='' AS empty_pass FROM mysql.user;"
# 2. 监听地址(0.0.0.0 = 全网可达,危险)
ss -tlnp | grep 3306
# 3. 版本(低版本有已知漏洞)
mysql -u root -p -e "SELECT VERSION();"
# 4. 测试数据库/测试账号是否删了
mysql -u root -p -e "SHOW DATABASES;"
# 5. 哪些用户有超级权限
mysql -u root -p -e "SELECT user, host, Super_priv FROM mysql.user WHERE Super_priv='Y';"中招的表现:
- 存在
''@'localhost'空密码账号 bind-address = 0.0.0.0且防火墙没挡test数据库还在- 应用连数据库用的是 root
二、账号与权限:最小权限原则
核心思想
每个应用用独立账号,只授它必需的权限。别再用 root 跑业务——应用被 SQL 注入攻破时,root 权限的数据库连接等于把整库送给攻击者。
创建规范的账号
-- 1. 删除匿名账号和测试数据库
DELETE FROM mysql.user WHERE User='';
DROP DATABASE IF EXISTS test;
DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%';
-- 2. 给应用建专用账号(举例:博客应用)
CREATE USER 'blog'@'localhost' IDENTIFIED BY '应用的独立强密码';
-- 3. 只授它需要的库和权限
GRANT SELECT, INSERT, UPDATE, DELETE ON blogdb.* TO 'blog'@'localhost';
-- 4. 如果应用在别的机器,指定来源 IP(不要用 %)
CREATE USER 'blog'@'192.168.1.%' IDENTIFIED BY '强密码';
GRANT SELECT, INSERT, UPDATE, DELETE ON blogdb.* TO 'blog'@'192.168.1.%';
-- 5. 备份账号只要读权限
CREATE USER 'backup'@'localhost' IDENTIFIED BY '另一个强密码';
GRANT SELECT, LOCK TABLES, SHOW VIEW, EVENT, TRIGGER ON *.* TO 'backup'@'localhost';
FLUSH PRIVILEGES;权限对照表
| 场景 | 需要的权限 | 绝不给 |
|---|---|---|
| 博客/CMS 应用 | SELECT, INSERT, UPDATE, DELETE | FILE, PROCESS, SUPER, GRANT |
| 只读查询账号 | SELECT | 其他全部 |
| 备份账号 | SELECT, LOCK TABLES, SHOW VIEW | 写权限 |
| 运维管理 | ALL(仅限 root,仅本地) | — |
高危权限说明:
FILE:能读写服务器文件(LOAD DATA INFILE、INTO OUTFILE),基本用不到,一定别给SUPER:能 kill 任意连接、改全局变量,给应用账号是灾难GRANT OPTION:能给别人授权,等于把数据库交出去PROCESS:能看到所有连接的 SQL,可能泄露敏感查询
root 账号加固
-- root 只允许本地登录
SELECT user, host FROM mysql.user WHERE user='root';
-- 如果 root 有 '%' 或公网 host,删掉:
DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost','127.0.0.1','::1');
-- 给 root 设强密码(如果还没设)
ALTER USER 'root'@'localhost' IDENTIFIED BY '超长随机密码';
FLUSH PRIVILEGES;三、网络层:别把 3306 暴露给全世界
MySQL 配置
# /etc/mysql/my.cnf 或 /etc/my.cnf
[mysqld]
# 只听本地(应用同机)或内网 IP(应用分离)
bind-address = 127.0.0.1
# 如果应用在别的机器:
# bind-address = 192.168.1.10
# 跳过 DNS 解析(加速+防 DNS 欺骗)
skip-name-resolve
# 禁止本地文件加载(防 INTO OUTFILE 写文件)
local-infile = 0
# symbolic-links = 0(防符号链接攻击)
symbolic-links = 0防火墙
# ufw: 默认拒,只放必要的
ufw default deny incoming
ufw allow from 192.168.1.0/24 to any port 3306 # 只允许内网应用段
ufw enable
# firewalld 富规则
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.1.0/24" port port="3306" protocol="tcp" accept'
firewall-cmd --reload云安全组
控制台安全组里 3306 端口:授权对象填具体 IP 或内网网段,绝不填 0.0.0.0/0。
NAT 型用户:确认服务商没有把 3306 做端口映射。映射了就去掉——数据库没有任何理由暴露公网。
必须公网访问怎么办
数据库跨公网访问是不得已的场景,正确做法:
SSH 隧道(最推荐,零额外组件):
# 本地执行,把远端 3306 映射到本地 13306 ssh -L 13306:localhost:3306 user@数据库服务器IP # 然后本地连 127.0.0.1:13306- VPN/专线:多机器互访走内网,别过公网
- SSL 连接:MySQL 8 支持强制 SSL,至少传输加密
四、传输与存储加密
连接加密(MySQL 5.7+)
-- 强制某账号必须 SSL 连接
ALTER USER 'blog'@'localhost' REQUIRE SSL;服务端配置:
[mysqld]
# 证书(用 Let's Encrypt 或自签)
ssl_ca = /etc/mysql/certs/ca.pem
ssl_cert = /etc/mysql/certs/server-cert.pem
ssl_key = /etc/mysql/certs/server-key.pem敏感字段加密
密码类字段必须哈希,不能明文:
-- 应用层用 password_hash() (PHP) 或 bcrypt 处理后再存
-- 数据库层也可以加一层(双保险,适合极敏感数据)
-- AES_ENCRYPT(字段, 密钥) / AES_DECRYPT(字段, 密钥)注意:密钥别存数据库里,放环境变量或密钥管理服务。数据库文件被拖走,没有密钥解不开。
五、审计与监控:出事能查
开启日志
[mysqld]
# 通用查询日志(记录所有 SQL,开销大,排障时临时开)
general_log = 1
general_log_file = /var/log/mysql/general.log
# 错误日志(必开)
log_error = /var/log/mysql/error.log
# 慢查询(性能+异常检测)
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2
# 二进制日志(主从+数据恢复,建议开)
log_bin = /var/log/mysql/mysql-bin
binlog_expire_logs_seconds = 604800 # 保留 7 天盯异常行为
# 1. 谁在连数据库
mysql -u root -p -e "SHOW PROCESSLIST;"
# 2. 失败登录尝试(在 error.log)
grep "Access denied" /var/log/mysql/error.log | tail -20
# 3. 大表扫描(可能的全库拖取)
grep "Examined_rows" /var/log/mysql/slow.log | awk '$NF > 100000' | tail
# 4. 用 fail2ban 封暴力破解的 IPfail2ban 配置:
# /etc/fail2ban/jail.d/mysql.local
[mysqld-auth]
enabled = true
filter = mysqld-auth
logpath = /var/log/mysql/error.log
maxretry = 3
bantime = 3600定期巡检清单
每周看一次:
# 账号清单(发现陌生账号立刻处理)
mysql -u root -p -e "SELECT user, host FROM mysql.user;"
# 权限清单
mysql -u root -p -e "SHOW GRANTS FOR 'blog'@'localhost';"
# 数据库大小异常增长(可能被塞了垃圾数据)
mysql -u root -p -e "
SELECT table_schema, ROUND(SUM(data_length)/1024/1024,1) AS MB
FROM information_schema.tables GROUP BY table_schema;"六、备份:最后一道防线
安全措施做得再好,也要有备份:
# 每日备份脚本
#!/bin/bash
DATE=$(date +%Y%m%d)
mysqldump -u backup -p'密码' --single-transaction --routines --triggers \
--all-databases | gzip > /backup/mysql-$DATE.sql.gz
# 保留 14 天
find /backup -name "mysql-*.sql.gz" -mtime +14 -delete# crontab 每天凌晨 3 点
0 3 * * * /opt/scripts/mysql-backup.sh >> /var/log/mysql-backup.log 2>&1关键:备份要加密、要外传(本地备份和数据库同生共死)。备份的恢复演练见游戏服备份与恢复里的验证方法,思路通用。
七、MySQL 安全配置模板
整合版,直接抄:
[mysqld]
# ===== 网络 =====
bind-address = 127.0.0.1
skip-name-resolve
local-infile = 0
symbolic-links = 0
# ===== 认证 =====
# MySQL 8 默认 caching_sha2_password,老客户端不兼容可改 mysql_native_password
default_authentication_plugin = caching_sha2_password
# 密码策略(validate_password 插件)
validate_password.policy = MEDIUM
validate_password.length = 12
# ===== 加密 =====
ssl_ca = /etc/mysql/certs/ca.pem
ssl_cert = /etc/mysql/certs/server-cert.pem
ssl_key = /etc/mysql/certs/server-key.pem
# ===== 日志 =====
log_error = /var/log/mysql/error.log
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2
log_bin = /var/log/mysql/mysql-bin
binlog_expire_logs_seconds = 604800
# ===== 资源限制(防单库吃光内存) =====
max_connections = 100
innodb_buffer_pool_size = 256M # 按机器内存的 50-70%改完:
systemctl restart mysql
mysql -u root -p -e "SELECT @@bind_address, @@have_ssl, @@local_infile;"八、常见问题
Q:应用和数据库必须分离,怎么安全互连?
同内网(同 VPC)直连最安全。跨公网用 SSH 隧道。两者都不行,至少强制 SSL + IP 白名单 + 强密码。
Q:validate_password 插件导致改密码失败?
密码要满足策略(长度、大小写、数字、特殊字符)。用 openssl rand -base64 16 生成,或者临时 SET GLOBAL validate_password.policy = LOW;(不推荐生产用)。
Q:怎么发现数据库被注入了?
看慢日志里的异常查询(UNION SELECT、SLEEP(5)、BENCHMARK)、检查表里有没有陌生的管理员账号、对比数据行数是否异常。
Q:MySQL 8 和 5.7 配置差别大吗?
认证方式、部分参数名有差异,但安全原则完全一样:最小权限 + 网络隔离 + 加密 + 审计。8 的默认配置已经安全很多。
Q:NAT 型机器跑数据库要注意什么?
确认 3306 没被映射到公网;用内网 IP 让同机应用连接;备份走对象存储外传。NAT 机型端口不默认开放,这点反而是优势。
小结
数据库加固核心四条:应用账号最小权限、3306 绝不暴露公网、连接和敏感数据加密、日志审计常看。再加每日备份和外传,数据丢了也能找回来。这一套做完,数据库从「最好打的靶子」变成「最难啃的骨头」。
更多安全教程见财富云计算知识库:Redis 未授权访问、防火墙配置、Web 漏洞防护、安全基线全系列。需要云服务器可以看看财富云计算:一键开服面板,NAT 型无独立公网 IP,价格比独立 IP 机型省一半以上。
延伸阅读
- 网站环境怎么选:LNMP 手动 / 宝塔面板 / Docker / 云开发
- 问卷表单系统搭建:SurveyKing / Formbricks,数据自己收
- 图床搭建:Lsky Pro / Lychee,自托管图片托管方案
- 邮件列表搭建:listmonk 自托管 Newsletter,把读者握在自己手里
- 文档站点搭建:MkDocs / VuePress / Docsify,产品文档与技术手册方案
- RSS 阅读器搭建:FreshRSS 自托管,把信息流拿回自己手里
- 短链接服务搭建:Shlink 自托管,链接数据自己掌握
- 网站状态监控页搭建:Uptime Kuma,服务器挂了第一时间知道

[...]安全组是云服务器的第一道门:配错了,要么玩家连不上、要么全网都能扫到你。这篇把安全组的原理、配法、排查讲透——从默认规则到最小化实践,从游戏服到网站,全部有现成规则表。需要现成的游戏云服务器,可以看看财富云计算:一键开服面板,NAT 型无独立公网 IP,价格比独立 IP 机型省一半以上,主流游戏都能快速部署,安全组模板已内置。一、安全组是什么安全组是云平台提供的虚拟防火墙,在流量到达你的服务器之前[...]
2026-10-15 回复
[...]), ...);导入 SQL 即可,附件在 usr/uploads 一起 scp 过去。五、权限与连接问题排查报错原因解法Access denied for user新库用户没建/密码错重建用户授权Unknown database库名拼错/没建SHOW DATABASES; 核对Table doesn't exist只导了部分表重新全库导出导入内容乱码字符集不一致全链路 utf8mb4,重导网站 [...]
2026-10-15 回复
[...]# 阿里云/腾讯云控制台看 AccessKey 调用记录如果仓库已经被 fork 过:历史里的密钥等于公开了,删仓库也没用——必须吊销密钥,这一步没有替代品。数据库密码泄露后的处置,参考数据库安全加固;确认是否已被入侵,走服务器被入侵应急排查。四、前端密钥的特殊风险前端 JS 里的密钥等于公开:// 错误:地图 API Key、推送 Key 写死在前端[...]
2026-10-15 回复
[...]# https://z0w.cn/wiki/database-security-hardening.htmlMySQL 的完整运维(安装、备份、主从、优化)见MySQL 安装与优化入门、MySQL 备份与恢复实战。七、常见疑问Q:SQLite 真的能扛几千 IP 的网站吗?能。Typecho + SQLite 扛日 IP 几千的博客是社区常态——读多写少的场景 SQLite 很从容。瓶颈通常在 P[...]
2026-10-18 回复
[...]netsh advfirewall firewall add rule name="GameRange" dir=in action=allow protocol=UDP localport=26901-26903完整的 Windows 环境准备(远程桌面连接、运行库安装、SteamCMD 用法、开机自启、8 个常见坑)见:《Windows 云服务器开服通用前置教程》→ htt[...]
2026-10-18 回复
[...]FLUSH PRIVILEGES;三个要点:utf8mb4(支持 emoji,utf8 会截断)、localhost only(数据库不对公网开放)、最小权限(业务账号不给 DDL 权限,见数据库安全加固与权限最小化)。2. Docker 起一个 MySQL:services:[...]
2026-10-18 回复