13 10 月 2026

数据库是网站和应用的命根子,也是入侵者的头号目标。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, DELETEFILE, 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 做端口映射。映射了就去掉——数据库没有任何理由暴露公网。

必须公网访问怎么办

数据库跨公网访问是不得已的场景,正确做法:

  1. SSH 隧道(最推荐,零额外组件):

    # 本地执行,把远端 3306 映射到本地 13306
    ssh -L 13306:localhost:3306 user@数据库服务器IP
    # 然后本地连 127.0.0.1:13306
  2. VPN/专线:多机器互访走内网,别过公网
  3. 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 封暴力破解的 IP

fail2ban 配置:

# /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 机型省一半以上。

延伸阅读
  1. 网站环境怎么选:LNMP 手动 / 宝塔面板 / Docker / 云开发
  2. 问卷表单系统搭建:SurveyKing / Formbricks,数据自己收
  3. 图床搭建:Lsky Pro / Lychee,自托管图片托管方案
  4. 邮件列表搭建:listmonk 自托管 Newsletter,把读者握在自己手里
  5. 文档站点搭建:MkDocs / VuePress / Docsify,产品文档与技术手册方案
  6. RSS 阅读器搭建:FreshRSS 自托管,把信息流拿回自己手里
  7. 短链接服务搭建:Shlink 自托管,链接数据自己掌握
  8. 网站状态监控页搭建:Uptime Kuma,服务器挂了第一时间知道
更多阅读
  1. 上一篇:Redis 未授权访问防护:从被入侵到彻底加固
  2. 下一篇:服务器端口安全:最小化开放原则与攻击面管理
发表评论 6

取消回复