📖 本篇简介:数据库安全是底线问题——root 远程登录、权限过大、密码明文、敏感数据不脱敏,任何一个都可能酿成数据泄露事故。本篇基于 MySQL 8.0,梳理账号权限、密码管理、审计、脱敏、SQL 注入、备份加密等 11 个高频坑。前置知识是 MySQL 账号体系。学完后你应能按最小权限原则管理账号、识别常见安全风险、并满足基本合规要求。
目录
- 坑 1:root 账号允许远程登录
- 坑 2:应用账号权限过大
- 坑 3:密码明文写在代码或配置里
- 坑 4:未开启审计日志
- 坑 5:敏感数据未脱敏
- 坑 6:SQL 注入防护不足
- 坑 7:备份数据未加密
- 坑 8:人员离职后权限未回收
- 坑 9:binlog 泄露敏感数据
- 坑 10:未限制数据库来源 IP
- 坑 11:弱密码与默认账号未清理
- 安全事件应急思路
- 数据库安全基线
- 速查表:安全检查项
- 自测练习
坑 1:root 账号允许远程登录
场景 / 错误示范:
为了方便远程管理,DBA 把 root 账号配成 root@'%',允许从任何 IP 登录。一旦密码泄露,攻击者可以从任意地方拿到最高权限。
-- 错误:root 允许任意主机登录(本示例基于 MySQL 8.0)
CREATE USER 'root'@'%' IDENTIFIED BY 'xxx';
-- root 是超级权限账号,从任意 IP 可登录,风险极高
为什么是坑:root 拥有数据库全部权限——读所有数据、改所有配置、删库。允许它从任意 IP 登录,等于把金库大门对着全网。一旦密码泄露或被爆破,攻击者直接拿到最高权限。超级账号必须限制来源,最好只允许本地登录。
正确做法:
root 只允许本地登录,远程管理用受限账号。
-- 正确:root 只允许本机登录(本示例基于 MySQL 8.0)
-- 默认 root@localhost,不要改成 root@'%'
-- 远程管理另建一个权限受限的账号
CREATE USER 'dba_admin'@'10.0.0.%' IDENTIFIED BY '强随机密码';
GRANT SELECT, PROCESS, REPLICATION CLIENT ON *.* TO 'dba_admin'@'10.0.0.%';
-- 只给运维需要的权限,不给 ALL PRIVILEGES
加分点:管理操作尽量走跳板机 + 堡垒机,不直接暴露数据库端口到公网。
坑 2:应用账号权限过大
场景 / 错误示范:
应用连接数据库的账号被授予了 ALL PRIVILEGES ON *.*,应用能删库、能改权限、能读其他业务库。一旦应用被 SQL 注入,攻击者直接拿到库级最高权限。
-- 错误:应用账号权限过大(本示例基于 MySQL 8.0)
GRANT ALL PRIVILEGES ON *.* TO 'app'@'%';
-- 应用只需对自己的库做增删改查,却拿到了全库全权
为什么是坑:应用账号应该只拥有"完成业务所需的最小权限"。给它 ALL PRIVILEGES ON *.*,等于把数据库命门交给应用——应用一旦被注入或攻破,攻击者能删库、拖数据、提权。最小权限原则是安全的基础,权限给多了就是给攻击者留后门。
正确做法:
按业务最小授权,一个应用一个账号。
-- 正确:应用账号只对自己的库有增删改查(本示例基于 MySQL 8.0)
CREATE USER 'order_app'@'10.0.0.%' IDENTIFIED BY '强随机密码';
GRANT SELECT, INSERT, UPDATE, DELETE ON order_db.* TO 'order_app'@'10.0.0.%';
-- 不给 DROP、ALTER、GRANT,不给其他库权限
加分点:不同业务用不同账号,一个业务被攻破不波及其他业务,是权限隔离的价值。
坑 3:密码明文写在代码或配置里
场景 / 错误示范:
数据库密码硬编码在源码里,提交到 Git 仓库。代码一公开/泄露,数据库密码就跟着泄露。
# 错误:密码明文写在代码里
conn = pymysql.connect(host="db", user="app", password="DbPass@2024")
为什么是坑:代码会被多人查看、提交到仓库、打包分发。密码硬编码在里面,泄露只是时间问题。更糟的是,历史提交里的密码即使后来删了,也还在 Git 历史里。密码必须和代码分离,放在专门的配置中心或环境变量。
正确做法:
密码放配置中心/环境变量,不进代码。
# 正确:密码从环境变量或配置中心读取
import os
conn = pymysql.connect(
host=os.environ["DB_HOST"],
user=os.environ["DB_USER"],
password=os.environ["DB_PWD"], # 不写死
)
加分点:如果密码已经误提交进 Git,必须立即改密码——光删代码没用,历史记录里还在。
坑 4:未开启审计日志
场景 / 错误示范:
数据库被人删了一张表,DBA 事后查谁干的、什么时候干的——因为没开审计日志,完全查不到,只能干瞪眼。
-- 错误:从不开启审计,出事无法追溯
-- 本示例基于 MySQL 8.0,默认不开审计
为什么是坑:审计日志记录"谁、在什么时候、对哪张表、做了什么操作"。没有它,误操作、恶意删改都无法追溯。合规要求(等保、数据安全法)通常也要求数据库有审计能力。审计不仅是安全防线,也是事后定责的依据。
正确做法:
开启审计,记录关键操作。
-- MySQL 8.0 可开启企业版审计插件,或用社区审计方案
-- 审计记录:登录、DDL、敏感表的增删改
-- 本示例基于 MySQL 8.0,具体按所用审计插件配置
-- 至少要能追溯:谁、何时、对哪张表、做了什么
加分点:审计日志本身也要保护——它记着敏感操作,自己不能被随意删改。
坑 5:敏感数据未脱敏
场景 / 错误示范:
用户手机号、身份证号在数据库里明文存储,DBA 一条 SELECT * 就能看到全部用户隐私。开发在测试环境也用真实手机号,测试库泄露就是数据泄露。
-- 错误:敏感信息明文存储,查询直接暴露
-- 本示例基于 MySQL 8.0
SELECT phone, id_card FROM t_user WHERE id = 1;
-- 138xxxx1234 / 370xxxx 全明文,谁都能看
为什么是坑:手机号、身份证、银行卡属于个人敏感信息,明文存储和展示,违反数据保护要求。一旦数据库或测试库泄露,就是大规模隐私泄露。敏感数据要脱敏——存储加密或哈希,展示时打码,测试环境用假数据。
正确做法:
敏感字段加密/哈希存储,展示脱敏。
-- 正确:敏感字段加密存储,展示时脱敏(本示例基于 MySQL 8.0)
-- 手机号存储加密,展示时打码
SELECT CONCAT(LEFT(phone,3), '****', RIGHT(phone,4)) AS phone_masked
FROM t_user WHERE id = 1;
-- 展示成 138****1234,不暴露完整号码
-- 身份证号可用哈希+加密,既支持查询又不暴露明文
SELECT id_card_hash FROM t_user WHERE id = 1;
加分点:测试、开发环境一律用脱敏后的假数据,绝不复制生产真实数据过去。
坑 6:SQL 注入防护不足
场景 / 错误示范:
应用拼接 SQL 字符串,把用户输入直接拼进去。攻击者输入 ' OR 1=1 --,查询被改成万能查询,拖走全表数据。
# 错误:字符串拼接 SQL,存在注入漏洞
sql = "SELECT * FROM t_user WHERE name = '" + user_input + "'"
cursor.execute(sql)
# 输入 ' OR '1'='1 -> SELECT * FROM t_user WHERE name = '' OR '1'='1' 全表拖走
为什么是坑:SQL 注入是数据库第一大安全漏洞。拼接用户输入进 SQL,攻击者可以篡改查询逻辑、拖数据、甚至删库。防护的根本是参数化查询——用户输入永远作为参数传递,不拼进 SQL 文本。
正确做法:
用参数化查询,绝不拼接。
# 正确:参数化查询,用户输入作为参数(本示例为 pymysql)
cursor.execute("SELECT * FROM t_user WHERE name = %s", (user_input,))
# 用户输入永远是值,不会被当成 SQL 逻辑执行
-- 正确:用预编译语句,参数与 SQL 分离(本示例基于 MySQL 8.0)
PREPARE stmt FROM 'SELECT * FROM t_user WHERE name = ?';
SET @name = '张三';
EXECUTE stmt USING @name;
加分点:最小权限的应用账号也是注入的最后一道防线——即使被注入,权限有限拖不走全库。
坑 7:备份数据未加密
场景 / 错误示范:
数据库备份文件明文存放在备份服务器。备份服务器被攻破,攻击者直接拿到整个库的数据,包括所有敏感信息。
# 错误:备份文件明文存放,谁拿到谁就能还原整个库
mysqldump db > db_backup.sql
为什么是坑:备份是数据库的完整副本,里面有全部敏感数据。它如果不加密、不隔离,就是一个"放着不动的完整数据泄露源"。备份文件被偷,等于数据库被拖走。备份的安全等级应该和生产库一样高。
正确做法:
备份文件加密存储,访问受限。
# 正确:备份加密后存放
mysqldump db | gpg -c -o db_backup.sql.gpg
# 备份文件加密,密钥单独保管,访问权限收紧
加分点:备份和生产库同样做访问控制——谁能拿备份、拿了要审批,不能因为它是"备份"就放松。
坑 8:人员离职后权限未回收
场景 / 错误示范:
员工从公司离职,但他的数据库账号没被禁用。几个月后这个账号还能登录数据库,成为安全隐患。
-- 错误:人员离职后账号未禁用(本示例基于 MySQL 8.0)
-- 'dev_zhangsan'@'%' 这个账号还活着,人已经走了
为什么是坑:人员离职、转岗后,他的数据库权限应该立即回收。留着不用的账号是"僵尸账号",一旦密码泄露或被利用,谁也不知道是谁在操作。账号生命周期管理——入职开通、离职回收——是基本安全流程。
正确做法:
离职即禁用/删除账号,定期审计账号列表。
-- 正确:离职立即禁用账号(本示例基于 MySQL 8.0)
ALTER USER 'dev_zhangsan'@'%' ACCOUNT LOCK; -- 锁定,不能登录
-- 确认无依赖后再 DROP USER
-- 定期审计:列出所有账号和来源
SELECT user, host, account_locked FROM mysql.user;
-- 发现长期不用的僵尸账号,清理或锁定
加分点:账号审计应定期做(如每季度),清理僵尸账号和过度授权。
坑 9:binlog 泄露敏感数据
场景 / 错误示范:
binlog 记录了所有数据变更,包括手机号、身份证的明文 UPDATE/INSERT。DBA 用 mysqlbinlog 解析 binlog 排查问题,敏感数据直接打印在屏幕上、写到日志里。
# 错误:解析 binlog 时敏感数据明文暴露
mysqlbinlog mysql-bin.000123 | grep "UPDATE t_user"
-- 手机号、身份证明文出现在输出里
为什么是坑:binlog 是数据变更的完整记录,里面包含所有敏感字段的明文。它常被用来排查问题,但解析时敏感数据就暴露在屏幕、日志、截图里。binlog 的访问权限和保留策略也要按敏感数据来管。
正确做法:
binlog 访问受限,解析时敏感字段注意脱敏。
# 正确:binlog 文件权限收紧,按需解析
# mysqlbinlog 解析时加 --base64-output=DECODE-ROWS -v 便于阅读
# 但输出含敏感数据,不要随意分享屏幕/日志
mysqlbinlog --base64-output=DECODE-ROWS -v mysql-bin.000123
加分点:敏感字段在表里就加密,binlog 自然也是密文,从源头减少暴露。
坑 10:未限制数据库来源 IP
场景 / 错误示范:
数据库账号授权成 'app'@'%',允许从任何 IP 连接。数据库端口又暴露在公网,攻击者可以从任意地方爆破密码。
-- 错误:账号不限制来源 IP(本示例基于 MySQL 8.0)
CREATE USER 'app'@'%' IDENTIFIED BY 'xxx'; -- % 意味着任意 IP
为什么是坑:'%' 允许从任何主机连接。如果数据库端口可达,攻击者就能从公网尝试爆破。限制来源 IP(比如只允许应用服务器网段),即使密码泄露,攻击者从外网也连不上。来源限制是网络层的第二道防线。
正确做法:
账号按来源网段授权。
-- 正确:只允许应用服务器网段连接(本示例基于 MySQL 8.0)
CREATE USER 'app'@'10.0.1.0/255.255.255.0' IDENTIFIED BY '强随机密码';
-- 只允许 10.0.1.x 网段,其他 IP 一律拒绝
加分点:数据库端口绝不直接暴露公网,通过内网/防火墙/安全组层层限制来源。
坑 11:弱密码与默认账号未清理
场景 / 错误示范:
数据库用了简单密码 123456,装完没删默认的 test 库和匿名账号。攻击者轻松爆破或利用默认账号进来。
-- 错误:弱密码 + 默认账号没清理(本示例基于 MySQL 8.0)
-- root 密码弱,或保留了匿名账号 ''@'localhost'
为什么是坑:弱密码是最容易被爆破的入口。MySQL 默认安装会有匿名账号、test 库,这些是历史遗留的安全隐患。新装数据库不清理默认项、不设强密码,等于没锁门。
正确做法:
强密码 + 清理默认账号。
-- 正确:强密码 + 清理默认(本示例基于 MySQL 8.0)
ALTER USER 'root'@'localhost' IDENTIFIED BY '强随机长密码';
-- 删除匿名账号
DELETE FROM mysql.user WHERE user='';
-- 删除 test 库
DROP DATABASE IF EXISTS test;
FLUSH PRIVILEGES;
-- 开启密码强度校验插件(本示例基于 MySQL 8.0)
INSTALL COMPONENT 'file://component_validate_password';
-- 强制密码长度、复杂度
SET GLOBAL validate_password.policy = 'STRONG';
加分点:强密码应足够长、含大小写数字符号,用密码管理器生成,不用人工记简单密码。
安全事件应急思路
发现数据库可能被入侵或泄露后,第一反应不是慌,而是按固定顺序处置。以下是一套可复用的应急路径。
| 步骤 | 做什么 | 目的 |
|---|---|---|
| 1. 止血 | 封禁来源 IP、锁定可疑账号、切断暴露面 | 先止损,别让破坏继续 |
| 2. 留证 | 保留审计日志、binlog、连接记录 | 事后追溯定责,别先删 |
| 3. 评估 | 查影响范围:哪些数据、哪些账号、多久 | 判断严重程度 |
| 4. 改密 | 轮换所有可能泄露的密码 | 堵住入口 |
| 5. 加固 | 补最小权限、收紧来源、开审计 | 防止再次发生 |
关键原则:先止血、再留证、后追查。发现异常时很多人第一反应是"先删掉痕迹",这恰恰毁掉了追溯证据。正确做法是冻结现场(保留日志),同时切断攻击者路径。
数据库安全基线
数据库安全不是单点操作,而是一套持续的基线。以下是最小可用的安全清单。
| 基线项 | 做法 | 风险 |
|---|---|---|
| 超级账号 | root 只本地登录 | 远程爆破拿最高权 |
| 应用账号 | 最小权限、按业务隔离 | 注入后被拖库 |
| 密码管理 | 配置中心、强密码 | 明文泄露 |
| 来源限制 | 按网段授权,不暴露公网 | 公网爆破 |
| 敏感数据 | 加密+脱敏 | 隐私泄露违规 |
| SQL 注入 | 参数化查询 | 拖库删库 |
| 审计 | 开启、保护审计日志 | 出事无法追溯 |
| 备份 | 加密存储 | 备份泄露 |
| 账号生命周期 | 离职即回收、定期审计 | 僵尸账号 |
核心认知:数据库安全的本质是"最小权限 + 层层设防"——超级账号不远程、应用账号最小权、来源 IP 受限、敏感数据脱敏、注入用参数化。没有哪一项能单独防住所有攻击,但每一项都在缩小攻击者得手后的破坏面。
合规视角:等保、数据安全法要求数据库有访问控制、审计、敏感数据保护。安全不是"出事后补",而是日常基线——审计一次账号权限,就可能堵住一个长期隐患。
速查表:安全检查项
| 检查项 | 风险做法 | 正确做法 |
|---|---|---|
| root 远程 | root@'%' | root 只 localhost |
| 应用权限 | ALL PRIVILEGES *.* | 最小库的增删改查 |
| 密码 | 硬编码代码 | 配置中心/环境变量 |
| 来源 IP | '@%' | 按网段授权 |
| 敏感数据 | 明文存储 | 加密+脱敏展示 |
| SQL 注入 | 字符串拼接 | 参数化查询 |
| 备份 | 明文存放 | 加密存储 |
| 审计 | 不开启 | 开启并保护日志 |
| 离职账号 | 不回收 | 即锁定+定期审计 |
自测练习
1. 为什么应用账号不能给 ALL PRIVILEGES ON *.*?
<details>
<summary>参考答案</summary>
违反最小权限原则。应用只需对自己的库增删改查,给全权意味着一旦被注入或攻破,攻击者能删库、拖全库、提权。应按业务最小授权,一个业务一个账号。
</details>
2. 防 SQL 注入的根本手段是什么?
<details>
<summary>参考答案</summary>
参数化查询(预编译语句),用户输入永远作为参数传递,不拼进 SQL 文本。字符串拼接 SQL 是注入根源。最小权限账号是注入后的第二道防线。
</details>
3. 敏感数据为什么要脱敏?测试环境怎么办?
<details>
<summary>参考答案</summary>
手机号、身份证等明文存储和展示违反数据保护要求,泄露即大规模隐私事件。存储应加密/哈希,展示打码;测试开发环境一律用脱敏假数据,绝不复制生产真实数据。
</details>
4. 为什么要限制数据库账号的来源 IP?
<details>
<summary>参考答案</summary>
'@%' 允许任意 IP 连接,端口可达时可被公网爆破。按应用服务器网段授权,即使密码泄露,外网也连不上。数据库端口绝不能直接暴露公网。
</details>
5. binlog 为什么也是敏感数据泄露源?
<details>
<summary>参考答案</summary>
binlog 完整记录所有数据变更,含敏感字段明文。解析排查时会暴露在屏幕/日志/截图里。应收紧 binlog 访问权限,敏感字段在表里就加密,从源头减少暴露。
</details>
下一篇预告:第 71 章 开发规范类踩坑
文章回复
0 条公开回复