# 12、Mysql数据库加固

LLMS index: [llms.txt](/wikis/llms.txt)

---

MySQL所使用的 SQL 语言是用于访问数据库的最常用标准化语言。MySQL 软件采用了双授权政策，分为社区版和商业版，由于其体积小、速度快、总体拥有成本低，尤其是开放源码这一特点，一般中小型网站的开发都选择 MySQL 作为网站数据库。

**mysql目录结构**

![](/wikis/_resources/12%E3%80%81MySQL%E6%95%B0%E6%8D%AE%E5%BA%93%E5%8A%A0%E5%9B%BA/78a12a48f71f294e461d1b50b8b3752a_MD5.jpg)

  


配置文件，也就是后缀为.cnf  
的文件，大家自己看看文件内容就知道每个文件的作用了。

我们来看mysql的帮助信息，里面有一条指明了mysql的配置文件是哪个，然后根据我们的情况就可以确定，mysql配置文件为/etc/mysql/my.cnf  
，因为其它两个文件不存在。

```plain
root@NF:~# mysql --help |grep -A 1 'Default options'
Default options are read from the following files in the given order:
/etc/my.cnf etc/mysql/my.cnf ~/.my.cnf
```

那我们查看一下/etc/mysql/my.cnf  
文件的内容（跟/etc/mysql/mysql.cnf  
内容一样）主要有下面这些信息，也说明了/etc/mysql/my.cnf  
是全局配置，~/.my.cnf  
（隐藏文件本环境下无此文件）是个人用户设置。

```plain
# The MySQL database server configuration file.
#
# You can copy this to one of:
# - "/etc/mysql/my.cnf" to set global options,
# - "~/.my.cnf" to set user-specific options.

!includedir /etc/mysql/conf.d/
#表示包含/etc/mysql/conf.d/这个路径下面的配置文件，前提是必须以为.cnf为后缀
!includedir /etc/mysql/mysql.conf.d/
#表示包含/etc/mysql/mysql.conf.d/这个路径下面的配置文件，前提是必须以为.cnf为后缀
```

然后我们看/etc/mysql/mysql.conf.d/mysql.conf.d，这个文件里面提供了mysql常用的基本配置。

那我们在此文件编辑mysql的配置，发现是可以失效的，它包含于/etc/mysql/my.cnf文件，所以编辑mysql配置的时候就编辑/etc/mysql/mysql.conf.d/mysql.conf.d文件即可。

其它的相关文件，其实就是理解linux各个主要目录是什么作用的。

```plain
/usr/lib/mysql        # 动态库文件（.so文件,so=shared object）
/usr/bin/mysql        # mysql命令，安装的软件的命令，usr指Unix System Resource
/usr/share/mysql      # mysql共享数据,主要是一些帮助文档
/etc/mysql            # mysql配置文件目录
/etc/init.d/mysql     # 服务管理脚本（启动，停止，关闭等）
/var/lib/mysql        # 默认的数据文档存储目录
/var/log/mysql        # mysql日志文件（查询语句记录，报错日志，慢查询日志等）
```

## 00 关注官方安全更新公告
在不影响业务正常运行的情况下，及时更新软件，打补丁。

https://www.oracle.com/security-alerts/

## 01 禁止数据库用户的密码为空并设置密码有效期
执行如下执行SQL语句检查密码是否为空：

```plain
select user,host from mysql.user where length(authentication_string) = 0;
或
select user,host,authentication_string,password_lifetime,account_locked from mysql.user;
```

![](/wikis/_resources/12%E3%80%81MySQL%E6%95%B0%E6%8D%AE%E5%BA%93%E5%8A%A0%E5%9B%BA/cb8c107c39d19d1068523d89d2cd0268_MD5.jpg)

  


若存在空密码的数据库用户，则执行如下命令设置数据库用户密码，且密码必须满足密码策略的要求：

```plain
set password for 'user'@'host' = password('yourpassword');
set password for 'testtest'@'192.168.56.1' = password('testtest');
```

**另外还要注意：**

1. 禁用或限制匿名、默认账户、测试账户的访问权限；（禁用账户）
2. 应重命名或删除默认账户，修改默认账户的默认口令；
3. 应及时删除或停用多余的、过期的账户，避免共享账户的存在；
4. 删除了默认数据库TEST。（旧版本会有默认的测试数据库）

**禁用数据库用户的语句**

```plain
ALTER USER 'user'@'host' ACCOUNT LOCK;
```

**查看密码有效期（全局变量）**

```plain
mysql> show global variables like 'default_password_lifetime';
+---------------------------+-------+
| Variable_name             | Value |
+---------------------------+-------+
| default_password_lifetime | 0     |
+---------------------------+-------+
1 row in set (0.00 sec)
```

配置密码有效期全局变量，编辑mysql配置文件，添加下面内容。

```plain
default_password_lifetime = 180
```

配置某个用户的密码有效期，使用ALTER USER命令为每个具体的用户账户单独设置特定的值，它会自动覆盖密码过期的全局策略。要注意ALTER USER语句的INTERVAL的单位是“天”。  

```plain
ALTER USER 'root'@'localhost' PASSWORD EXPIRE INTERVAL 180 DAY;
```

## 02 检查数据库用户的密码是否为弱口令
有些人为了方便，可能会把数据库用户的密码设置为弱口令，现在的数据库会以mysql5加密算法加密口令，可以去MD5解密的平台输入密文，看能否得出明文。

https://www.cmd5.com/

https://www.somd5.com/

![](/wikis/_resources/12%E3%80%81MySQL%E6%95%B0%E6%8D%AE%E5%BA%93%E5%8A%A0%E5%9B%BA/a2bd0d6e3d1ce39dddf6e3f1a104589a_MD5.jpg)

  


如果是弱口令，则要求更改其数据库用户的口令。

  


关于MySQL密码你应该知道的那些事 - cenalulu

MySQL：密码加密方式 - xuejianbest

## 03   密码复杂度配置
在数据库配置文件/etc/mysql/mysql.conf.d/mysqld.cnf中添加如下配置（根据自身业务需要设置密码复杂度策略）

```plain
[mysqld]
plugin-load = "validate_password.so"
validate-password = FORCE_PLUS_PERMANENT
validate_password_length = 8 
validate_password_policy = 1 
validate_password_mixed_case_count = 1 
validate_password_number_count = 1 
validate_password_special_char_count = 1
```

  


validate-password = FORCE_PLUS_PERMANENT  
值为FORCE_PLUS_PERMANENT  
表示强制启用该插件，并且不能被卸载。

validate_password_policy  
表示密码策略，有三个值，与其对应的策略见下表：

  


| 值 | 密码策略 |
| --- | --- |
| 0  或  LOW | 校验密码长度变量 |
| 1  或  MEDIUM | 校验密码长度、数字、小写/大写和特殊字符4个变量 |
| 2  或  STRONG | 校验密码长度、数字、小写/大写、特殊字符和字典文件5个变量 |


  


简言之，对于密码数字、小写/大写和特殊字符这三个变量，只有当密码策略为中或强时才是有效的。

其它参数含义见下面的引用文章。

那么最低要求配置如下

效果：密码不能与用户名一致，密码长度8位以上（包含8位）、至少有一个数字、一个大写字母、一个小写字母、一个特殊字符。

```plain
mysql> show variables like '%validate_password%';
+--------------------------------------+--------+
| Variable_name                        | Value  |
+--------------------------------------+--------+
| validate_password_check_user_name    | OFF    |
|---|---|
| validate_password_dictionary_file    |        |
| validate_password_length             | 8      |
| validate_password_mixed_case_count   | 1      |
| validate_password_number_count       | 1      |
| validate_password_policy             | MEDIUM |
| validate_password_special_char_count | 1      |
+--------------------------------------+--------+
7 rows in set (0.00 sec)
```

安装和卸载插件

validate_password插件相关参数的介绍

MySql5.6使用validate password 插件加强密码强度的安装及使用方法 - wangmm0218

## 04 登录失败和连接超时设置
mysql有个连接超时的插件，相当于登录失败锁定策略，可根据业务需要进行最低配置。

默认配置如下：

```plain
mysql> show variables like "%connection_control%";
+-------------------------------------------------+-------+
| Variable_name                                   | Value |
+-------------------------------------------------+-------+
| connection_control_failed_connections_threshold | 3     |
|---|---|
| connection_control_max_connection_delay         | 86400 |
| connection_control_min_connection_delay         | 1000  |
+-------------------------------------------------+-------+
3 rows in set (0.00 sec)
```

connection_control_failed_connections_threshold  
失败尝试的次数，默认为3，表示当连接失败3次后启用连接控制0表示不开启  
connection_control_max_connection_delay  
响应延迟的最大时间  
connection_control_min_connection_delay  
响应延迟的最小时间，默认1000微秒，1秒

然后是超时时间设置。

查看和设置 连接超时相关的两个参数interactive_timeout和wait_timeout，其值应当至多为30分钟。

```plain
show global variables like 'interactive_timeout';
show global variables like 'wait_timeout'; 

set global interactive_timeout=1800;
set global wait_timeout=1800;
```

interactive_timeout：交互式连接超时时间(mysql工具、mysqldump等)  
wait_timeout：非交互式连接超时时间、默认的连接mysql api程序、jdbc连接数据库等

简单来说，通过mysql客户端连接数据库是交互式连接，通过jdbc连接数据库是非交互式连接。

  


连接控制插件安装

MySQL安全插件：Connection-Control Plugins 的利与弊 - leonpenn

MySQL 插件之 连接控制插件(Connection-Control) - ZhenXing_Yu

MySQL连接超时相关的两个参数interactive_timeout和wait_timeout的区别和解释 - young5201314

MySQL参数max_connect_errors分析释疑 - 潇湘隐者

MySQL状态变量Aborted_connects与Aborted_clients浅析 -海东潮

## 05 启用SSL
查看是否启用SSL（如果启用了SSL需要进行配置才能正常远程连接管理）。

validate-password = FORCE_PLUS_PERMANENT  
值为FORCE_PLUS_PERMANENT  
表示强制启用该插件，并且不能被卸载。

validate_password_policy  
表示密码策略，有三个值，与其对应的策略见下表：

| 值 | 密码策略 |
| --- | --- |
| 0  或  LOW | 校验密码长度变量 |
| 1  或  MEDIUM | 校验密码长度、数字、小写/大写和特殊字符4个变量 |
| 2  或  STRONG | 校验密码长度、数字、小写/大写、特殊字符和字典文件5个变量 |


简言之，对于密码数字、小写/大写和特殊字符这三个变量，只有当密码策略为中或强时才是有效的。

其它参数含义见下面的引用文章。

那么最低要求配置如下

效果：密码不能与用户名一致，密码长度8位以上（包含8位）、至少有一个数字、一个大写字母、一个小写字母、一个特殊字符。

```plain
mysql> show variables like '%validate_password%';
+--------------------------------------+--------+
| Variable_name                        | Value  |
+--------------------------------------+--------+
| validate_password_check_user_name    | OFF    |
|---|---|
| validate_password_dictionary_file    |        |
| validate_password_length             | 8      |
| validate_password_mixed_case_count   | 1      |
| validate_password_number_count       | 1      |
| validate_password_policy             | MEDIUM |
| validate_password_special_char_count | 1      |
+--------------------------------------+--------+
7 rows in set (0.00 sec)
```

安装和卸载插件

validate_password插件相关参数的介绍

MySql5.6使用validate password 插件加强密码强度的安装及使用方法 - wangmm0218

## 04 登录失败和连接超时设置
mysql有个连接超时的插件，相当于登录失败锁定策略，可根据业务需要进行最低配置。

默认配置如下：

```plain
mysql> show variables like "%connection_control%";
+-------------------------------------------------+-------+
| Variable_name                                   | Value |
+-------------------------------------------------+-------+
| connection_control_failed_connections_threshold | 3     |
|---|---|
| connection_control_max_connection_delay         | 86400 |
| connection_control_min_connection_delay         | 1000  |
+-------------------------------------------------+-------+
3 rows in set (0.00 sec)
```

connection_control_failed_connections_threshold  
失败尝试的次数，默认为3，表示当连接失败3次后启用连接控制，0表示不开启  
connection_control_max_connection_delay  
响应延迟的最大时间  
connection_control_min_connection_delay  
响应延迟的最小时间，默认1000微秒，1秒

然后是超时时间设置。

查看和设置 连接超时相关的两个参数interactive_timeout和wait_timeout，其值应当至多为30分钟。

```plain
show global variables like 'interactive_timeout';
show global variables like 'wait_timeout'; 

set global interactive_timeout=1800;
set global wait_timeout=1800;
```

interactive_timeout：交互式连接超时时间(mysql工具、mysqldump等)  
wait_timeout：非交互式连接超时时间、默认的连接mysql api程序、jdbc连接数据库等

简单来说，通过mysql客户端连接数据库是交互式连接，通过jdbc连接数据库是非交互式连接。

  


连接控制插件安装

MySQL安全插件：Connection-Control Plugins 的利与弊 - leonpenn

MySQL 插件之 连接控制插件(Connection-Control) - ZhenXing_Yu

MySQL连接超时相关的两个参数interactive_timeout和wait_timeout的区别和解释 - young5201314

MySQL参数max_connect_errors分析释疑 - 潇湘隐者

MySQL状态变量Aborted_connects与Aborted_clients浅析 -海东潮

## 05 启用SSL
查看是否启用SSL（如果启用了SSL需要进行配置才能正常远程连接管理）。

  


```plain
mysql> show variables like '%ssl';
+---------------+----------+
| Variable_name | Value    |
+---------------+----------+
| have_openssl  | DISABLED |
|---|---|
| have_ssl      | DISABLED |
+---------------+----------+
2 rows in set (0.01 sec)
```

说明：如果数据库禁止远程管理（select user,host from mysql.user;  
），则已经符合安全要求，此情况下已无需启用SSL。

MySQL SSL配置（mysql5.7和mysql5.6) - Yuki_xiong

MYSQL SSL配置与使用 - 德莱華

## 06 远程管理限制
其实最好还是应当禁止远程登录至少要禁止root直接远程登录管理数据库。

执行select user,host from mysql.user where user='root';

看访问地址是否仅为 127.0.0.1 或 localhost 或 ::1

其它用户如需远程连接，应做访问范围限制。

执行select user,host from mysql.user where host = '%';

若host字段为：% （允许任何IP连接），则说明不合规。

  


**远程连接管理配置方法：**

```plain
GRANT ALL PRIVILEGES ON <databases-name>.* TO 'user'@'<ip>' IDENTIFIED BY '<password>' WITH GRANT OPTION;
FLUSH PRIVILEGES;
```

  


<databases-name>  指定单个数据库名 也可以用 *（即所有数据库名）

user 指定一个数据库用户名

<ip> 指定一个IP、一个网段（包括B段、C段 192.168.1.%）、%（所有IP）

<password> 指定远程连接时使用的密码，与本地密码可不同（但需符合密码复杂度要求）

```plain
# 举例，给数据库用户teacher分配student数据库，只允许192.168.56.%网段远程连接并设置口令为Admin123。
GRANT ALL PRIVILEGES ON student.* TO 'teacher'@'192.168.56.%' IDENTIFIED BY 'Admin123' WITH GRANT OPTION;
FLUSH PRIVILEGES;
```

效果如下

  


![](/wikis/_resources/12%E3%80%81MySQL%E6%95%B0%E6%8D%AE%E5%BA%93%E5%8A%A0%E5%9B%BA/4f66d3677b056939070945bbe5ba8359_MD5.jpg)

  


最后可以在mysql.user表中看到账号的情况，也是从这里删除。

```plain
select user,host from mysql.user where account_locked='N' and host!='localhost';
drop user 'user'@'host';
```

**知识补充-drop和delete的区别**

+ drop  
drop user XXX;删除已存在的用户，默认删除的是'XXX'@'%'这个用户，如果还有其他的用户(其它主机名),如'XXX'@'localhost'等，不会一起被删除。如果要删除'XXX'@'localhost'，使用drop删除时需要加上host即drop user 'XXX'@'localhost'。
+ delete  
delete from user where user='XXX' and host='localhost';其中XXX为用户名，localhost为主机名(即需指定主机名)。
+ drop和delete的区别  
drop不仅会将user表中的数据删除，还会删除其他权限表的内容。而delete只删除user表中的内容，所以使用delete删除用户后需要执行FLUSH PRIVILEGES;刷新权限，否则下次使用create语句创建用户时会报错。

## 07 会话连接数配置
配置同样是在mysql配置文件中添加相关参数（max_connections = 100）。

```plain
mysql> show variables like "%connections";
+----------------------+-------+
| Variable_name        | Value |
+----------------------+-------+
| max_connections      | 100   |
|---|---|
| max_user_connections | 0     |
+----------------------+-------+
2 rows in set (0.01 sec)
```

max_connections是对整个服务器的用户做出限制，  
max_user_connections是对每个用户的限制，  
为0表示不限制。

```plain
root@NF:~$ grep max_connections etc/mysql/mysql.conf.d/mysqld.cnf
max_connections        = 100
```

MySQL参数最大连接数max_connections -  paul_hch

## 08 启用日志审计
mysql默认启用日志审计，记录的内容也符合相关安全要求，此项默认符合。

查看日志启用情况

查看变量，看相关日志是否启用show variables like 'log%';

或看mysql的配置文件，看相关日志的配置情况（根据业务需要启用相关日志和设置日志保存路径。）

```plain
# * Logging and Replication
#
# Both location gets rotated by the cronjob.
# Be aware that this log type is a performance killer.
# As of 5.1 you can enable the log at runtime!
#通用日志，将所有到达MySQL Server的SQL语句记录下来
general_log_file        = var/log/mysql/mysql.log
general_log             = 1
log_timestamps = SYSTEM
#
# Error log - should be very few entries.
#错误日志,文件内容不会很多
log_error = var/log/mysql/error.log
#
# Here you can see queries with especially long duration
#慢查询日志，记录SQL执行语句（执行时间超过2秒才会记录）
slow_query_log          = 1
slow_query_log_file     = var/log/mysql/mysql-slow.log
long_query_time = 2
log-queries-not-using-indexes
#
# The following can be used as easy to replay backup logs or for replication.
# note: if you are setting up a replication slave, see README.Debian about
#       other settings you may need to change.
#二进制日志
server-id               = 1
log_bin                 = var/log/mysql/mysql-bin.log
expire_logs_days        = 10
max_binlog_size   = 100M
#binlog_do_db           = include_database_name
#binlog_ignore_db       = include_database_name
```

来看一下日志内容，日志默认是分天存储的，一天一个文件并压缩保存。

+ 相关日志（主要是查询日志和错误日志）应留存6个月以上。
+ 日志记录的日期和时间应当是正确的，服务器需开启了NTP服务进行时间校对。  


![](/wikis/_resources/12%E3%80%81MySQL%E6%95%B0%E6%8D%AE%E5%BA%93%E5%8A%A0%E5%9B%BA/ebaf76c8a8b694e97cf0f3b127bd9bc8_MD5.jpg)

![](/wikis/_resources/12%E3%80%81MySQL%E6%95%B0%E6%8D%AE%E5%BA%93%E5%8A%A0%E5%9B%BA/427d3f64216747a183b99a95c272068f_MD5.jpg)

```plain
#Linux 检查NTP服务时间同步情况
ntpq -p -n
ntpstat
```

## 09 禁止.mysql_history文件记录信息
.mysql_history文件会记录MySQL操作历史（即数据库查询语句），包含敏感信息。为了避免敏感信息泄露，需要禁止使用。  
检查所有.mysql_history文件是否链接到dev/null，若没连接到，则以root用户执行如下命令：

```plain
find  -name ".mysql_history" | xargs
rm <your_path>/.mysql_history 
ln -s dev/null <your_path>/.mysql_history
```

![](/wikis/_resources/12%E3%80%81MySQL%E6%95%B0%E6%8D%AE%E5%BA%93%E5%8A%A0%E5%9B%BA/f75bc5ae51ff8d2eae128dbcaf6719f8_MD5.jpg)

**10 禁止mysql对系统文件进行读写操作**  


local_infile变量表示能否使用load data local infile命令。该变量默认为ON。该变量为OFF时，禁用客户端使用load data local infile命令。避免通过数据库查询语句造成的任意文件读写漏洞。

执行如下SQL语句：

```plain
show variables like 'local_infile';
```

若返回结果不为OFF，则在/etc/my.cnf配置文件中修改

```plain
[mysqld] 
local_infile = 0
```

## 11  用户权限合理分配
执行下面语句，查看各账户和权限分配情况。请根据业务需求进行合理的权限分配，应遵循三权分立原则（分为系统管理员、安全管理员、安全审计员等，并检查系统各用户所属的权限组。如：系统管理员不能进行业务操作、审计操作审计员不能进行业务操作、系统管理操作安全员不能进行添加账号操作等）

```plain
select user,host,account_locked from mysql.user;
show grants for 'user'@'host';
select * from mysql.user where user='user' and host='host' \G;
```

+ 不能存在特权用户
+ 不存在越权访问情况（绕过访问控制策略）
+ mysql 数据库应当只允许root用户进行访问和管理

## others
**1.其它建议**

+ 最小权限原则1.对于数据库，可以一个数据库用户分配一个数据库2.对于mysql进程，不得以root用户运行，默认是采用了mysql用户运行。
+ 更改默认开放端口3306
+ 站库分离

**2.一些常见语句记录**

```plain
SET GLOBAL default_password_lifetime = 180;
#设置全局变量及赋值。
INSTALL PLUGIN validate_password SONAME 'validate_password.so';
#安装插件，这里是安装配置密码复杂度策略的插件。
```

**3.关于更改数据库用户密码**

```plain
update user set password=password('123') where user='root' and host='localhost';
# mysql 5.7以下

update mysql.user set authentication_string=PASSWORD('newpassword') where user='username' and host='localhost';
# mysql 5.7以上

alter user 'root'@'localhost' identified by 'newpassword';
# mysql 8.0以上
```

其实就是要注意密码的列名是什么，版本不一样，列名不同，可使select * from mysql.user \G;查看密码的列名。（\G 即把列数据逐行显示）
