前言

网络安全无小事,网络不仅仅是技术活,也是一个仔细活。

而任何系统部署在互联网上,不仅仅在外的业务系统有可能存在漏洞,部署在后面的数据库也一样可能存在漏洞。

MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS (Relational Database Management System,关系数据库管理系统) 应用软件之一。如何做好Mysql的安全设置,也是整体网络安全的必要一环。

通常会说数据库的权限按最小权限为原则,但是很多时候并不清楚具体如何权限最小?需要哪些具体权限?所以很多人mysql用着root账户在操作,或者即使新建了用户,也是为了方便而授予全部权限。同时mysql部署后,也有一些权限或其他配置,很多人都会忽略。

mysql有哪些权限


权限

权限级别

权限说明

CREATE

数据库、表或索引

创建数据库、表或索引权限

DROP

数据库或表

删除数据库或表权限

GRANT OPTION

数据库、表或保存的程序

赋予权限选项

REFERENCES

数据库或表


ALTER

更改表,比如添加字段、索引等

DELETE

删除数据权限

INDEX

索引权限

INSERT

插入权限

SELECT

查询权限

UPDATE

更新权限

CREATE VIEW

视图

创建视图权限

SHOW VIEW

视图

查看视图权限

ALTER ROUTINE

存储过程

更改存储过程权限

CREATE ROUTINE

存储过程

创建存储过程权限

EXECUTE

存储过程

执行存储过程权限

FILE

服务器主机上的文件访问

文件访问权限

CREATE TEMPORARY TABLES

服务器管理

创建临时表权限

LOCK TABLES

服务器管理

锁表权限

CREATE USER

服务器管理

创建用户权限

PROCESS

服务器管理

查看进程权限

RELOAD

服务器管理

执行flush-hosts, flush-logs, flush-privileges, flush-status, flush-tables, flush-threads, refresh, reload等命令的权限

REPLICATION CLIENT

服务器管理

复制权限

REPLICATION SLAVE

服务器管理

复制权限

SHOW DATABASES

服务器管理

查看数据库权限

SHUTDOWN

服务器管理

关闭数据库权限

SUPER

服务器管理

执行kill线程权限

数据库层面(db表)的权限分析


权限

说明

网站使用账户是否给予

Select   

可对其下所有表进行查询

建议给予

Insert            

可对其下所有表进行插入

建议给予

Update               

可对其下所有表进行更新

建议给予

Delete                   

可对其下所有表进行删除

建议给予

Create                  

可在此数据库下创建表或者索引

建议给予

Drop                 

可删除此数据库,及此数据库下的表

不建议给予

Grant               

赋予权限选项

不建议给予

References             

未来MySQL特性的占位符

不建议给予

Index                

可对其下的所有表进行索引

建议给予

Alter                  

可对其下的所有表进行更改

建议给予

Create_tmp_table          

创建临时表

不建议给予

Lock_tables             

可对其下所有表进行锁定

不建议给予

Create_view              

可在此数据下创建视图

建议给予

Show_view             

可在此数据下查看视图

建议给予

Create_routine         

可在此数据下创建存储过程

不建议给予

Alter_routine        

可在此数据下更改存储过程

不建议给予

Execute         

可在此数据下执行存储过程

不建议给予

Event               

可在此数据下创建事件调度器

不建议给予

Trigger

可在此数据下创建触发器

不建议给予

mysql入侵提权分析及防止措施

一般来说,mysql的提权有这么几种方式:

udf提权

此方式的关键导入一个dll文件,个人认为只要合理控制了进程账户对目录的写入权限即可防止被导入dll文件;然后如果万一被攻破,此时只要进程账户的权限够低,也没办执行高危操作,如添加账户等。

写入启动文件

这种方式同上,还是要合理控制进程账户对目录的写入权限。

当root账户被泄露

如果没有合理管理root账户导致root账户被入侵,此时数据库信息肯定是没办法保证了。但是如果对进程账户的权限控制住,以及其对磁盘的权限控制,服务器还是能够保证不被沦陷的。

普通账户泄露

上述所说的,只对某个库有所有权限的账户

此处说的普通账户指网站使用的账户,我给的一个比较方便的建议是直接给予特定库的所有权限。账户泄露包括存在注入及web服务器被入侵后直接拿到数据库账户密码。

此时,对应的那个数据库数据不保,但是不会威胁到其他数据库。而且这里的普通账户无file权限,所有不能导出文件到磁盘,当然此时还是会对进程的账户的权限严格控制。

普通账户给予什么样的权限可以见上表,实在不会就直接给予一个库的所有权限。


mysql主要安全配置

目录权限设置

为支持漏洞扫描,可以做如下设置:

我们把Mysql安装在 /usr/local/mysql目录下,我们必须建立一个用户名为mysql,组为mysql的用户来运行我们的mysql,同时我们把它的配置文件拷贝到 /etc目录下:

# cp suport-files/my-medium.cnf /etc/my.cnf

chown root:sys /etc/my.cnf

chmod 644 /etc/my.cnf

使用用户mysql来启动我们的mysql:

# /usr/local/mysql/bin/mysqld_safe -user=mysql &

# chown -R root  /usr/local/mysql/  //mysql主目录给root
# chown -R mysql.mysql /usr/local/mysql/var //确保数据库目录权限所属mysql用户

window下,创建一个非管理员用户mysql,然后在my.ini里mysqld段里申明user=mysql,然后修改mysql windows服务,选择这个这个用户logon。

修改root用户的的口令

缺省安装的mysql是没有密码的,所以我们要修改。为了安全起见,必须修改为强密码,所谓的强密码,至少8位,由字母、数字和符号组成的不规律密码。使用MySQL自带的命令mysaladmin修改root密码,同时也可以登陆数据库,修改数据库mysql下的user表的字段内容。

下面采用三种方式来修改root的口令。

  • 用mysqladmin命令来改root用户口令
# mysqladmin -uroot password test

这样,MySQL数据库root用户的口令就被改成test了。(test只是举例,我们实际使用的口令一定不能使用这种易猜的弱口令)

  • 用set password修改口令:
mysql> set password for root@localhost=password('test');

这时root用户的口令就被改成test了。

  • 直接修改user表的root用户口令
• mysql> use mysql;
mysql> update user set password=password('test') where user='root';
mysql> flush privileges;

这样,MySQL数据库root用户的口令也被改成test了。其中最后一句命令flush privileges的意思是强制刷新内存授权表,否则用的还是缓冲中的口令,这时非法用户还可以用root用户及空口令登陆,直到重启MySQL服务器。

删除默认的数据库和用户

我们的数据库是在本地,并且也只需要本地的php脚本对mysql进行读取,所以很多用户不需要。mysql初始化后会自动生成空用户和test库,这会对数据库构成威胁,我们全部删除。 我们使用mysql客户端程序连接到本地的mysql服务器后出现如下提示:

#mysql> show databases;
mysql> drop database test;
mysql> use mysql;
mysql> delete from db;
#mysql> delete from user where not (user='root') ; // 删除初始非root的用户
mysql> delete from user where not(host="localhost" and user="root");
mysql> flush privileges;v

改变默认mysql管理员的名称

这个工作是可以选择的,根据个人习惯,因为默认的mysql的管理员名称是root,所以如果能够修改的话,能够防止一些脚本小子对系统的穷举。我们可以直接修改数据库,把root用户改为"aiguan"

mysql> use mysql;
mysql> update user set user="aiguan" where user="root";
mysql> flush privileges;

提高本地安全性

提高本地安全性,主要是防止mysql对本地文件的存取,比如黑客通过mysql把/etc/passwd获取了,会对系统构成威胁。mysql对本地文件的存取是通过SQL语句来实现,主要是通过Load DATA LOCAL INFILE来实现,我们能够通过禁用该功能来防止黑客通过SQL注射等获取系统核心文件。

禁用该功能必须在 my.cnf 的[mysqld]部分加上一个参数:

set-variable=local-infile=0

windows下是:

loose-local-infile =0

禁止远程连接mysql

因为我们的mysql只需要本地的php脚本进行连接,所以我们无需开socket进行监听,那么我们完全可以关闭监听的功能。 有两个方法实现:

  • 配置my.cnf文件,在[mysqld]部分添加 skip-networking 参数
  • mysqld服务器中参数中添加 --skip-networking 启动参数来使mysql不监听任何TCP/IP连接,增加安全性。如果要进行mysql的管理的话,可以在服务器本地安装一个phpMyadmin来进行管理。

在命令行netstat -ant下看到,默认的3306端口是打开的,此时打开了mysqld的网络监听,允许用户远程通过帐号密码连接数本地据库,默认情况是允许远程连接数据的。为了禁止该功能,启动skip-networking,不监听sql的任何TCP/IP的连接,切断远程访问的权利,保证安全性。假如需要远程管理数据库,可通过安装PhpMyadmin来实现。假如确实需要远程连接数据库,至少修改默认的监听端口,同时添加防火墙规则,只允许可信任的网络的mysql监听端口的数据通过。

# vim /etc/my.cf
将#skip-networking注释去掉。
# /usr/local/mysql/bin/mysqladmin -u root -p shutdown //停止数据库
#/usr/local/mysql/bin/mysqld_safe --user=mysql & //后台用mysql用户启动mysql

控制数据库访问权限

对于使用php脚本来进行交互,最好建立一个用户只针对某个库有 update、select、delete、insert、drop table、create table等权限,这样就很好避免了数据库用户名和密码被黑客查看后最小损失。 比如下面我们创建一个数据库为db1,同时建立一个用户test1能够访问该数据库。

mysql> create database db1;
mysql> grant select,insert,update,delete,create,drop on db1.* to test1@localhost identified by 'admindb';

以上SQL是创建一个数据库db1,同时增加了一个test1用户,口令是admindb,但是它只能从本地连接mysql,对db1库有select,insert,update,delete,create,drop操作权限。

限制一般用户浏览其他用户数据库

如果有多个数据库,每个数据库有一个用户,那么必须限制用户浏览其他数据库内容,可以在启动MySQL服务器时加--skip-show-database 启动参数就能够达到目的。

忘记mysql密码的解决办法

如果不慎忘记了MySQL的root密码,我们可以在启动MySQL服务器时加上参数--skip-grant-tables来跳过授权表的验证 (./safe_mysqld --skip-grant-tables &),这样我们就可以直接登陆MySQL服务器,然后再修改root用户的口令,重启MySQL就可以用新口令登陆了。

设置最大连接数

在[mysqld]段中添加或修改max_connections值,

默认的值(可能是156)

max_connections=512

数据库的某用户多次远程连接,会导致性能的下降和影响其他用户的操作,有必要对其进行限制。可以通过限制单个账户允许的连接数量来实现,设置my.cnf文件的mysqld中的max_user_connections变量来完成。GRANT语句也可以支持 资源控制选项来限制服务器对一个账户允许的使用范围。

#vim /etc/my.cnf
[mysqld]
max_user_connections 2

全方位设置日志

#log
#错误日志
log-error="E:\mysql5.6\mysql_error.log"
log_output=FILE             
 #不启用的话慢日志查询会存在数据表中
general_log=on
general_log_file=E:\mysql5.6\mysql_query.log
#慢查询日志
slow_query_log=on   
#slow-query-log=1
long_query_time =2  
 #慢于2秒的会被记录
slow_query_log_file=E:\mysql5.6\mysql_slowquery.log
 
#二进制日志配置,第二、三行配置最后一个binlog-bin和binlog是不加后缀的文件名,不加后缀的话有妙用
#server-id=1
#log_bin=E:\mysql5.6\log_bin\binlog-bin
#log_bin_index=E:\mysql5.6\log_bin\binlog
log-bin="bin-log"

数据库文件的安全

我们默认的mysql是安装在/usr/local/mysql目录下的,那么对应的数据库文件就是在/usr/local/mysql/var目录下,那么我们要保证该目录不能让未经授权的用户访问后把数据库打包拷贝走了,所以要限制对该目录的访问。 我们修改该目录的所属用户和组是mysql,同时改变访问权限:

chown -R mysql.mysql /usr/local/mysql/var
chmod -R go-rwx /usr/local/mysql/var

删除历史记录

执行以上的命令会被shell记录在历史文件里,比如bash会写入用户目录的.bash_history文件,如果这些文件不慎被读,那么数据库的密码就会泄漏。用户登陆数据库后执行的SQL命令也会被MySQL记录在用户目录的.mysql_history文件里。如果数据库用户用SQL语句修改了数据库密码,也会因.mysql_history文件而泄漏。所以我们在shell登陆及备份的时候不要在-p后直接加密码,而是在提示后再输入数据库密码。 另外这两个文件我们也应该不让它记录我们的操作,以防万一。

rm .bash_history .mysql_history
ln -s /dev/null .bash_history
ln -s /dev/null .mysql_history

限制访问mysql端口的ip

windows可以通过windows防火墙或者ipsec来限制,linux下可以通过iptables来限制。

修改mysql的端口

windows下可以修改配置文件my.ini来实现,linux可以修改配置文件my.cnf来实现。默认端口是3306,这个最好修改下,为了方便记忆,你可以根据的ip地址来加密动态调整,不过如果生产网络允许,也可以定期修改,最好不要影响研发进度。

对所有用户设置强密码并严格指定对应账号的访问ip

mysql中可在user表中指定用户的访问可访问ip

root特权账号的处理

建议给root账号设置强密码,并指定只容许本地登录,尽量并且不要使用固定密码,实行每个用户单独密码,长度在16位以上 0-9a-zA-Z~!@#$%^&*()-+ 随机组合。根据业务的情况设定密码过期时间,定期更改,同时不可使用重复密码。为了方便管理,可能会采用一个密码表,要加强对于密码表的维护更新,最重要的是保证不泄漏。

日志的处理

如需要可开启查询日志,查询日志会记录登录和查询语句。

mysql进程运行账号

在windows下禁止使用local system来运行mysql账户,可以考虑使用network service或者自己新建一个账号,但是必须给与mysql程序所在目录的读取权限和data目录的读取和写入权限; 在linux下,新建一个mysql账号,并在安装的时候就指定mysql以mysql账户来运行,给与程序所在目录的读取权限,data所在目录的读取和写入权限。

mysql运行账号的磁盘权限

1)mysql运行账号需要给予程序所在目录的读取权限,以及data目录的读取和写入权限 
2)不容许给予其他目录的写入和执行权限,特别是有网站的。 
3)取消mysql运行账户对于cmd,sh等一些程序的执行权限。

网站使用的mysql账户的处理

新建一个账户,给予账户在所使用数据库的所有权限即可。这样既能保证网站对所对应的数据库的全部操作,也能保证账户不会因为权限过高而影响安全。给予单个数据库的所有权限的账户不会拥有super, process, file等管理权限的。 当然,如果能很明确是的知道,我的网站需要哪些权限,还是不要多给权限,因为很多时候发布者并不知道网站需要哪些权限,我才建议上面的配置。而且我指的通用的,具体到只有几台机器,不多的情况下,我个人建议还是给予只需要的权限,具体可参考上面的表格的建议。

密码的安全管理

密码是数据库安全管理的一个很重要因素,不要将纯文本密码保存到数据库中。如果你的计算机有安全危险,入侵者可以获得所有的密码并使用它们。相反,应使用MD5()、SHA1()或单向哈希函数。也不要从词典中选择密码,有专门的程序可以破解它们,请选用至少八位,由字母、数字和符号组成的强密码。在存取密码时,使用mysql的内置函数password()的sql语句,对密码进行加密后存储。例如以下方式在users表中加入新用户。

#mysql> insert into users values (1,password(1234),'test');

使用独立用户运行msyql

绝对不要作为使用root用户运行MySQL服务器。这样做非常危险,因为任何具有FILE权限的用户能够用root创建文件(例如,~root/.bashrc)。mysqld拒绝使用root运行,除非使用–user=root选项明显指定。应该用普通非特权用户运行mysqld。正如前面的安装过程一样,为数据库建立独立的linux中的mysql账户,该账户用来只用于管理和运行MySQL。

要想用其它Unix用户启动mysqld,,增加user选项指定/etc/my.cnf选项文件或服务器数据目录的my.cnf选项文件中的[mysqld]组的用户名。

#vim /etc/my.cnf
[mysqld]
user=mysql

该命令使服务器用指定的用户来启动,无论你手动启动或通过mysqld_safe或mysql.server启动,都能确保使用mysql的身份。也可以在启动数据库是,加上user参数。

# /usr/local/mysql/bin/mysqld_safe --user=mysql &

作为其它linux用户而不用root运行mysqld,你不需要更改user表中的root用户名,因为MySQL账户的用户名与linux账户的用户名无关。确保mysqld运行时,只使用对数据库目录具有读或写权限的linux用户来运行。

禁止MySQL对本地文件存取

在mysql中,提供对本地文件的读取,使用的是load data local infile命令,默认在5.0版本中,该选项是默认打开的,该操作令会利用MySQL把本地文件读到数据库中,然后用户就可以非法获取敏感信息了,假如你不需要读取本地文件,请务必关闭。

测试:首先在测试数据库下建立sqlfile.txt文件,用逗号隔开各个字段

# vi sqlfile.txt
1,sszng,111
2,sman,222
#mysql> load data local infile 'sqlfile.txt' into table users fields terminated by ','; //读入数据
#mysql> select * from users;
+--------+------------+----------+
| userid  | username   | password |
+--------+------------+----------+
|      1 | sszng    | 111   |
|      2 | sman    | 222  |
+--------+------------+----------+

成功的将本地数据插入数据中,此时应该禁止MySQL中用“LOAD DATA LOCAL INFILE”命令。网络上流传的一些攻击方法中就有用它LOAD DATA LOCAL INFILE的,同时它也是很多新发现的SQL Injection攻击利用的手段!黑客还能通过使用LOAD DATALOCAL INFILE装载“/etc/passwd”进一个数据库表,然后能用SELECT显示它,这个操作对服务器的安全来说,是致命的。可以在my.cnf中添加local-infile=0,或者加参数local-infile=0启动mysql。

#/usr/local/mysql/bin/mysqld_safe --user=mysql --local-infile=0 &
#mysql> load data local infile 'sqlfile.txt' into table users fields terminated by ',';
#ERROR 1148 (42000): The used command is not allowed with this MySQL version

--local-infile=0

选项启动mysqld从服务器端禁用所有LOAD DATA LOCAL命令,假如需要获取本地文件,需要打开,但是建议关闭。

使用chroot方式来控制MySQL的运行目录

Chroot是linux中的一种系统高级保护手段,它的建立会将其与主系统几乎完全隔离,也就是说,一旦遭到什么问题,也不会危及到正在运行的主系统。这是一个非常有效的办法,特别是在配置网络服务程序的时候。

关闭对Web访问的支持

如果不打算让Web访问使用MySQL数据库,没有提供诸如PHP这样的Web语言的时候,重新设置或编译你的PHP,取消它们对MySQL的默认支持。假如服务器中使用php等web程序,试试用Web形式非法的请求,如果得到任何形式的MySQL错误,立即分析原因,及时修改Web程序,堵住漏洞,防止MySQL暴露在web面前。
对于Web的安全检查,在MySQL官方文档中这么建议,对于web应用,至少检查以下清单:
    试试用Web形式输入单引号和双引号(‘’’和‘”’)。如果得到任何形式的MySQL错误,立即分析原因。
    试试修改动态URL,可以在其中添加%22(‘”’)、%23(‘#’)和%27(‘’’)。
    试试在动态URL中修改数据类型,使用前面示例中的字符,包括数字和字符类型。你的应用程序应足够安全,可以防范此类修改和类似攻击。
    试试输入字符、空格和特殊符号,不要输入数值字段的数字。你的应用程序应在将它们传递到MySQL之前将它们删除或生成错误。将未经过检查的值传递给MySQL是很危险的!
    将数据传给MySQL之前先检查其大小。
    用管理账户之外的用户名将应用程序连接到数据库。不要给应用程序任何不需要的访问权限。

数据库备份策略

一般可采用本地备份和网络备份的形式,可采用MySQL本身自带的mysqldump的方式和直接复制备份形式,

直接拷贝数据文件最为直接、快速、方便,但缺点是基本上不能实现增量备份。为了保证数据的一致性,需要在备份文件前,执行以下 SQL 语句:FLUSH TABLES WITH READ LOCK;也就是把内存中的数据都刷新到磁盘中,同时锁定数据表,以保证拷贝过程中不会有新的数据写入。这种方法备份出来的数据恢复也很简单,直接拷贝回原来的数据库目录下即可。

使用mysqldump可以把整个数据库装载到一个单独的文本文件中。这个文件包含有所有重建您的数据库所需要的SQL命令。这个命令取得所有的模式(Schema,后面有解释)并且将其转换成DDL语法(CREATE语句,即数据库定义语句),取得所有的数据,并且从这些数据中创建INSERT语句。这个工具将您的数据库中所有的设计倒转。因为所有的东西都被包含到了一个文本文件中。这个文本文件可以用一个简单的批处理和一个合适SQL语句导回到MySQL中。

使用 mysqldump进行备份非常简单,如果要备份数据库” nagios_db_backup ”,使用命令,同时使用管道gzip命令对备份文件进行压缩,建议使用异地备份的形式,可以采用Rsync等方式,将备份服务器的目录挂载到数据库服务器,将数据库文件备份打包在,通过crontab定时备份数据:

#!/bin/sh
time=`date +"("%F")"%R`
$/usr/local/mysql/bin/mysqldump -u nagios -pnagios nagios | gzip >/home/sszheng/nfs58/nagiosbackup/nagios_backup.$time.gz
# crontab -l
# m h  dom mon dow   command
00 00 * * * /home/sszheng/shnagios/backup.sh

恢复数据使用命令:

gzip -d nagios_backup.(2008-01-24)00:00.gz
nagios_backup.(2008-01-24)00:00
#mysql –u root -p nagios       <  /home/sszheng/nfs58/nagiosbackup/nagios_backup.(2008-01-24)12:00

安全配置需要的常用命令

建一个用户并给予相应数据库的权限

grant select,insert,update,delete,create,drop privileges on database.* to user@localhost identified by 'passwd';

grant all privileges on database.* to user@localhost identified by 'passwd';

刷新权限

flush privileges;

显示授权

show grants;

移除授权

revoke delete on *.* from 'jack'@'localhost';

删除用户

drop user 'jack'@'localhost';

给用户改名

rename user 'jack'@'%' to 'jim'@'%';

给用户改密码

SET PASSWORD FOR 'root'@'localhost' = PASSWORD('123456');

删除数据库

drop database test;

从数据库导出文件

select * from a into outfile "d:\abc.vbs"

Mysqld安全相关启动选项

下列mysqld选项影响安全:
    --allow-suspicious-udfs该选项控制是否可以载入主函数只有xxx符的用户定义函数。默认情况下,该选项被关闭,并且只能载入至少有辅助符的UDF。这样可以防止从未包含合法UDF的共享对象文件载入函数。
    --local-infile[={0|1}]
如果用–local-infile=0启动服务器,则客户端不能使用LOCAL in LOAD DATA语句。
    --old-passwords
强制服务器为新密码生成短(pre-4.1)密码哈希。当服务器必须支持旧版本客户端程序时,为了保证兼容性这很有用。
     (OBSOLETE) --safe-show-database
在以前版本的MySQL中,该选项使SHOW DATABASES语句只显示用户具有部分权限的数据库名。在MySQL 5.1中,该选项不再作为现在的 默认行为使用,有一个SHOW DATABASES权限可以用来控制每个账户对数据库名的访问。
    --safe-user-create
如果启用,用户不能用GRANT语句创建新用户,除非用户有mysql.user表的INSERT权限。如果你想让用户具有授权权限来创建新用户,你应给用户授予下面的权限:
mysql> GRANT INSERT(user) ON mysql.user TO ‘user_name’@'host_name’;
这样确保用户不能直接更改权限列,必须使用GRANT语句给其它用户授予该权限。
    --secure-auth
不允许鉴定有旧(pre-4.1)密码的账户。
    --skip-grant-tables
这个选项导致服务器根本不使用权限系统。这给每个人以完全访问所有的数据库的权力!(通过执行mysqladmin flush-privileges或mysqladmin eload命令,或执行FLUSH PRIVILEGES语句,你能告诉一个正在运行的服务器再次开始使用授权表。)
    --skip-name-resolve
主机名不被解析。所有在授权表的Host的列值必须是IP号或localhost。
    --skip-networking
在网络上不允许TCP/IP连接。所有到mysqld的连接必须经由Unix套接字进行。
    --skip-show-database
使用该选项,只允许有SHOW DATABASES权限的用户执行SHOW DATABASES语句,该语句显示所有数据库名。不使用该选项,允许所有用户执行SHOW DATABASES,但只显示用户有SHOW DATABASES权限或部分数据库权限的数据库名。请注意全局权限指数据库的权限。