一、前言
- 对于任何一个企业来说,其数据库系统中所保存数据的安全性无疑是非常重要的,尤其是公司的有些商业数据,可能数据就是公司的根本。
- 失去了数据,可能就失去了一切
- 本章将针对mysql的安全相关内容进行较为详细的介绍。
二、数据库系统安全相关因素
1、外围网络
让我们的mysql处在一个有保护的局域网之中,而不是置于开发的公网中。
2、主机
第二层防线“主机层防线”,“主机层防线“主要拦截网络(包括局域网)或者直连的未授权用户试图入侵主机的行为。
3、数据库
第三道防线“数据库防线”,也就是mysql数据库系统自身的访问控制授权管理相关模块。
这道防线基本上可以说是mysql的最后一道防线了,也是最核心最重要的防线。
之前的两层防线对于所有数据库系统来说基本上区别不大,都存在者基本相同的各种威胁,不论是Oracle还是mysql,以及其他的数据库管理系统,都需要基本一致的“布防”策略。
但是这第三层防线,也就是各自自身的“数据库防线”对于每个数据库系统来说都存在较大差异,因为每种数据库都有各自不太一样的专门负责访问授权相关的模块。不论是权限划分还是实现方式都可能不太一样。
MySql的访问授权相关模块主要是由两部分组成,一个是基本的用户管理模块;另一个是访问授权控制模块。
- 用户管理模块相对简单,主要是负责用户登陆连接相关的基本权限控制,但其在安全控制方面的作用不比任何环节小。他就像mysql的一个“大门门卫”一样,通过校验每一位敲门者所给的进门“暗号”(登入口令),决定是非给敲门者开门。
- 访问控制模块则是随时随地检查已经进门的访问者,校验他们是否有访问所发出请求需要访问的数据的权限。通过校验者可以顺利拿到数据,而未通过校验的访问者,只能收到“访问越权”的相关反馈。
4、代码
1)SQL语句相关安全因素
“SQL注入攻击”指的就是攻击者根据数据库的SQL语句解析器的原理,利用程序中对客户端所提交数据的校验漏洞,从而通过程序动态提交数据接口提交非法数据,达到攻击者的入侵目的。
“SQL注入攻击“的破坏性非常大,轻者造成数据被窃取,重者数据遭到破坏,甚至可能丢失全部的数据。
2)程序代码相关安全因素
程序代码如果权限校验不够仔细而存在安全漏洞,则同样可能会被入侵者利用,达到窃取数据等目的。
比如一个存在安全漏洞的信息管理系统,很容易就可能窃取到其他一些系统的登入口令。之后,就能堂而皇之的轻松登陆到其他相关系统达到窃取相关数据的目的。甚至还可能通过应用系统中保存不善的数据库系统连接登陆口令,从而带来更大的损失。
三、MySQL权限系统介绍
1、权限系统简介
msql的权限系统在实现上比较简单,相关权限信息主要存储在几个被称为grant tables的系统表中,即:mysql.User,mysql.db,mysql.Host,mysql.table_priv和mysql.column_priv。
由于权限信息数据量比较小,而且访问比较频繁,所以mysql在启动的时候,就会将所有的权限信息都load到内存中保存在几个特定的结构中。
所以才有了我们每次手工修改了权限相关的表之后,都需要执行“flush privileges”命令重新加载mysql的权限信息。
当然,如果我们通过grant、revoke或者drop user命令来修改相关权限,则不需要手工执行“flush privileges”命令,因为在使用grant等来修改系统表的同时,也会修改内存结构中的权限信息。
在mysql5.0.2或更高版本的时候,mysql还增加了CREATE USER命令,以此创建无任何特别权限(仅拥有初始USAGE权限)的用户,通过CREATE USER命令创建了新用户后,新用户的信息也会自动更新到内存结构中。
所以,建议读者一般情况下尽量使用GRANT、REVOKE、CREATE USER以及DROP USER命令来进行用户和权限的变更操作,尽量减少直接修改grant tables来实现用户和权限变更的操作。
2、权限授予与去除
要为某个用户授权,可以使用GRANT命令,要去除某个用户已有的权限则使用REVOKE命令。
当然除了这两个命令之外,还有一种比较暴力的办法,那就是直接更新grant tables系统表。
当给某个用户授权的时候,不仅需要指定用户名,同时还要指定来访主机。
如果授权的时候仅指定用户名,则mysql会自动认为是对“username@%”授权。
要去除某个用户的权限同样也需要指定来访主机。
可能有些时候我们还需要查看某个用户目前拥有的权限,这可以通过两个方式实现,首先是通过执行“show grants for 'username'@'hostname'”命令来获取之前该用户身上的所有授权。另一种方法是查询grant tables里边的权限信息。
3、权限级别
1)Global Level
- Global Level的权限控制也叫全局权限控制,所有权限信息都保存在mysql.user表中。
- Global Level的所有权限都是针对整个mysqld的,对所有的数据库下的所有表及所有字段都有效。
- 如果一个权限是以Global Level来授予的,则会覆盖其他所有级别的相同权限设置。
- 比如我们首先给abc用户授权可以update指定数据库如test的t表,然后又在全局级别REVOKE掉了abc用户对所有数据库的所有表的UPDATE权限,那么这时候的abc用户将不再拥有对test.t表的更新权限。
- 要授予Global Level的权限,则只需要在执行GRANT命令的时候,用“*.*”来指定适用范围是Global的即可,当有多个权限需要授予的时候,也并不需要多次重复执行GRANT命令,只需要一次将所有需要的权限名称通过逗号(“,”)隔开即可,如:GRANT SELECT,UPDATE,DELETE,INSERT ON *.* TO 'def'@'localhost';
2)Database Level
- Database Level是在Global Level之下,其他三个Level之上的权限级别,其作用域即为所指定整个数据库中的所有对象。
- 与Global Level相比,Database Level主要少了以下几个权限:CREATE USER、FILE、PROCESS、RELOAD、REPLICATION CLIENT、REPLICATION SLAVE、SHOW DATABASES、SHUTDOWN、SUPER和USAGE这几个权限,没有增加任何权限。
- 之前我们说Global Level的权限会覆盖底下其他四层的相同权限,Database Level也一样,虽然他可能被Global Level的权限所覆盖,但同时他也能覆盖比他更下层的Table、Column和Routine这三层的权限。
- 如果要授予Database Level的权限,则可以有两种实现方式:
a.在执行GRANT命令的时候,通过“database.*”来限定权限作用域为database整个数据库:GRANT ALTER ON test.* TO 'def'@'localhost';
b.先通过USE命令选定需要授权的数据库,然后通过“*”来限定作用域,这样授权的作用域实际上就是当前选定的数据库 - 在授予权限的时候,如果有相同的权限需要授予多个用户,我们也可以在授权语句中一次写上多个用户信息,用逗号分隔开就可以了,如:grant create on perf.* to 'abc'@'localhost','def'@'localhost';
3)Table Level
- Database Level之下就是Table Level的权限了,Table Level的权限可以被Global Level 和Database Level的权限所覆盖,同时也能覆盖Column Level 和Routine Level 的权限。
- Table Level 的权限作用范围是授权语句中所指定数据库的指定表。如可以通过如下语句给test数据库的t1表授权:GRANT INDEX ON test.t1 TO 'abc'@'%.jianzhaoyang.com';
- Table Level的权限由于其作用域仅限于某个特定的表,所以权限种类也比较少,仅有ALTER、CREATE、DELETE、DROP、INDEX、INSERT、SELECT、UPDATE这八种权限。
4)Column Level
- Column Level的权限作用范围就更小了,仅仅是某个表的指定的某个列。
- 由于权限覆盖的原则,Column Level的权限同样可以被Global、Database、Table这三个级别的权限中的相同级别所覆盖,而且由于Column Level所针对的权限和Routine Level的权限作用域没有重合部分,所以不会有覆盖与被覆盖的关系。
- Column Level的权限只有INSERT、SELECT和UPDATE这三种。
- Column Level的权限授权语句语法基本和Table Level差不多,只是需要在权限名称后面将需要授权的列名列表通过括号括起来:GRANT SELECT(id,value) ON test.t2 TO 'abc'@'%.jianzhaoyang.com';
- 当某个用户在向某个表插入数据的时候,如果该用户在该表中某列上面没有INSERT权限,则该列的数据将以默认值填充。这一点和很多其他的数据库有一些区别,是mysql自己在SQL上面的扩展。
5)Routine Level
- Routine Level的权限主要只有EXECUTE和ALTER ROUTINE两种,主要的针对的对象是procedure 和function这两种对象。
- 在授予Routine Level权限的时候,需要指定数据库和相关对象,如:GRANT EXECUTE ON test.p1 to 'abc'@'localhost';
6)GRANT权限
- 除了上述几类权限外,还有一个非常特殊的权限GRANT,拥有GRANT权限的用户可以将自身所拥有的任何权限全部授予其他任何用户,所以GRANT权限是一个非常特殊也非常重要的权限。
- GRANT权限的授予方式也和其他任何权限都不太一样。
- 通常是通过在执行GRANT授权语句的时候在最后添加WITH GRANT OPTION子句达到授予GRANT权限的目的。
- 另外,我们还可以通过GRANT ALL语句授予某个Level的所有可用权限给某个用户,如: grant all on test.t5 to 'abc';
- 以上五个Level的权限中,Table、Column和Routine三者在授权中所依赖(或引用的)对象必须是已经存在的,而不像Database Level的权限授予,可以在当前不存在该数据库的时候完成授权。
4、MySQL访问控制实现原理
- MySQL的访问控制实际上由两个功能模块共同组成,一个是负责“看守mysql大门”的用户管理模块,另一个就是负责监控来访者每一个动作的访问控制模块。
- 用户管理模块决定造访客人能否进门,而访问控制模块则决定每个客人进门能拿什么不能拿什么。
- mysql中实现简单访问控制的流程图如下:
-
用户管理:
(1)在mysql中,用户访问控制部分的实现比较简单,所有授权用户都存放在一个系统表中:mysql.user,当然这个表不仅存放了授权用户的基本信息,还存放有部分细化的权限信息。
(2)用户管理模块需要使用的信息很少,主要就是Host、User、Password这三项,都在mysql.user表中
(3)一个用户想要访问mysql,至少需要提供上面列出的三项数据,mysql才能判断是否该让它进门。
(4)这三项实际是由两部分组成:来访者来源的主机名(或主机IP地址信息)和访问者的来访“暗号”(登陆用户名和登陆密码),这两部分中的任何一个没有能够匹配上都无法让看守大门的用户管理模块乖乖开门。
(5)其中Host信息存放的是mysql允许所对应的User的信任主机,可以是某个具体的主机名或域名,也可以是用“%”来充当通配符的某个域名集合,也可以是一个具体的IP地址,同样也可以是存在通配符的域名集合,还可以用“%”来代表任何主机,就是不对访问者的主机做任何限制。 -
访问控制:
(1)当客户端连接通过用户管理模块的验证,可连接上mysql server之后,就会发送各种Query和Command给mysql server,以实现客户端应用的各种功能。
(2)当mysql接收到客户端的请求之后,访问控制模块是需要校验该用户是否满足提交的请求所需要的权限。
(3)权限校验过程是从最大范围往最小范围的权限开始依次校验所涉及到的每个对象的每个权限。
(4)在验证所需权限的时候,mysql首先会查找存储在内存结构中的权限数据,首先查找Global Level权限,如果所需权限在Global Level都有定义(GRANT或REVOKE),则完成权限校验(通过或者拒绝);
(5)如果没有找到所有权限的定义,则会继续查找Database Level的权限,进行Global Level未定义的所需权限的校验,如果仍然没有找到所有所需权限的定义,则会继续往更小范围的权限定义域查找,也就是Table Level、Column Level或者Routine Level。
(6)在前面我们了解到mysql的grant tables有mysql.user、mysql.db、mysql.host、mysql.table_priv和mysql.column_priv这五个。
(7)我想除了mysql.host之外的四个都非常容易理解,每一个表都针对mysql的一种逻辑对象,存放某一特定Leve的权限,唯独mysql.host稍有区别。
(8)我们来看看mysql.host权限表在mysql访问控制中充当了什么样的角色?
【1】mysql.host在mysql访问控制模块中所实现的功能比较特殊,和其他几个grant tables不太一样。
【2】首先mysql.host中的权限数据不是通过GRANT或REVOKE来授予或者去除,而是必须手工通过INSESRT、UPDATE和DELETE命令来修改其中的数据。
【3】其次是其中的权限数据无法单独生效,必须通过和mysql.db权限表的数据一起才能生效。
【4】而且仅当mysql.db总存在不完整的时候,才会促使访问控制模块再结合mysql.host中查找是否有相应的补充权限数据实现以达到权限校验的目的。
【5】在mysql.db中无法找到满足权限校验的所有条件的数据,则说明在mysql.db中无法完成权限校验,所以也不会直接校验db.select_priv的值是否是Y。
(9)mysql的权限授予至少需要用户名和主机名二者才能确定一个访问者的权限。
(10)mysql如何确定权限信息?实际上,mysql永远优先考虑更精确范围的权限。
(11)在mysql内部会按照username和hostname做一个排序,对于相同username的权限,其host信息越接近访问者的来源host,则排序位置越靠前,则越早被校验使用到。
(12)而且mysql在权限校验过程中,只要找到匹配的权限之后,就不会再继续往后查找是否还有匹配的权限信息,而直接完成校验过程。
四、MySQL访问授权策略
1、前言
- 在我们了解了数据库系统安全的相关因素和mysql权限系统的工作原理之后,就需要为我们的系统设计一个安全合理的授权策略。
- 我想,每个人都清楚,想要授权最简单最简便方便,维护工作量最少,那自然是将所有权限都授予所有的用户来的最简单方便了。
- 但是,一个用户所拥有的权限越大,那么他给我们的系统所带来的潜在威胁也就越大。
- 所以从安全方面考虑,权限自然是授予的越小越好。一个有足够安全意识的管理员在授权的时候,都会只授权必要的权限,而不会授予任何多余的权限。
2、授权策略:
- 我们从安全的角度来考虑如何设计一个更为安全合理的授权策略。
-
首先需要了解来访主机:
(1)由于mysql数据库登陆验证用户的时候是除了用户名和密码之外,还要验证来源主机,所以我们还需要了解每个用户可能从哪些主机发起连接。
(2)当然,我们也可以通过授权的时候直接通过“%”通配符来给所有主机授予都有访问的权限,但是这样就违背了我们安全策略的原则,带来了潜在风险,所以并不可取。
(3)尤其是在没有局域网防火墙保护的情况下,更是不能轻易允许可以从任何主机登陆的用户存在。
(4)能通过具体主机名和IP地址指定的尽量通过使用具体的主机名和IP地址来限定来访主机,不能用具体的主机名和IP地址限定的也需要用尽可能小的通配范围来限定。 -
其次,了解用户需求:
(1)既然要做到仅授予必要的权限,那么我们必须了解每个用户所担当的角色,也就是说,我们充分了解每个用户需要连接到数据库上完成什么工作。
(2)了解用户是一个只读应用的用户,还是一个读写都有的用户,是一个备份作业的用户还是一个日常管理的用户,是只需要访问特定的数据库,还是需要访问所有的数据库。
(3)只有了解了需要做什么,才能准确的了解需要授予什么样的权限。
(4)因为如果权限过低,会造成工作无法正常完成,而权限过高,则存在潜在的安全风险。 -
再次,要为工作分类:
(1)为了做到各司其职,我们需要将需要做的工作分门别类,不同类别的工作使用不同的用户,做好用户分离。
(2)虽然这样可能会带来管理成本方面的部分工作量增加,但是基于安全方面的考虑,这部分管理工作量的增加是非常值得的。
(3)而且我们所要做的分离也只是适度的分离。比如将执行备份工作、复制工作、常规应用访问、只读应用访问和日常管理工作分别分理出单独的特定账户来授予各自所需权限。
(4)这样,既可以让安全风险尽量降低,也可以让同类同级别的相似权限合并在一起,不互相交织在一起。
(5)对于PROCESS、FILE和SUPER这样的特殊权限,仅仅只有管理类账号才需要,不应该授予其他非管理账号。 -
最后,确保只有绝对必要者拥有GRANT OPTION权限:
(1)之前在权限系统介绍的时候我们已经了解到GRANT OPTION权限的特殊性,和拥有该权限之后的潜在风险,所以在这里就不赘述了。
(2)总之,为了安全考虑,拥有GRANT OTPION权限的用户越少越好,尽可能只让拥有超级权限的用户才拥有GRANT OPTION权限。
五、安全设置注意事项
- 使用私有局域网。我们可以通过使用私有局域网,通过网络设备,统一私有局域网的出口,并通过网络防火墙设备控制出口的安全。
- 使用SSL加密通道。如果我们对数据保密要求非常严格,可以启用mysql提供的ssl访问接口,将传输数据进行加密。使网络传输的数据即使被截获,也无法轻易使用。
- 访问授权限定来访主机信息。我们可以在授权的时候,通过指定主机的主机名、域名或IP地址信息来限定来访主机的范围。
- OS安全方面。关闭mysql Server主机上面任何不需要的服务,这不仅能从安全方面减少潜在隐患,还能减少主机的部分负担,尽可能提高性能。
- 使用网络扫描工具(如nmap等)扫描主机端口,检查除了mysql需要监听的端口3306之外,还有哪些端口是打开正在监听的,并去不必要的端口。
- 严格控制OS账号的管理,以防账号信息外泄,尤其是root和mysql账号。
- 对root和mysql等对mysql的相关文件有特殊操作权限的OS账号登陆后做出比较显眼的提示,并在Terminal的提示信息中输出当前用户信息,以防止操作的时候经过多次用户切换后出现人为误操作。
- 用非root用户运行mysql。因为如果使用root运行mysql,那么myslqd的进程就会拥有root用户所拥有的权限,任何具有FILE权限的用户就可以在mysql中向系统中的任何位置写入文件。
- 文件和进程安全。合理设置文件的权限属性,mysql的相关数据和日志和所在文件夹属主和所属组都设置为mysql,且禁用其他所有用户的读写权限。以防止数据或者日志文件被窃取或破坏。
- 确保mysql server所在的主机上所必要运行的其他应用或服务足够安全,避免因为其他应用或者服务存在安全漏洞而被入侵者攻破防线。
- 用户设置。我们必须确保任何可以访问数据库的用户都有一个比较复杂的内容作为密码,而不是非常简单或者比较有规律的字符,以防止被使用字典破解程序攻破。
- 在 MySQL初始安装完成之后,系统中可能存在一个不需要任何密码的root用户,有些版本安装完成之后还会存在一个可以通过localhost登录的没有用户名和密码的帐号。这些帐号会给系统带来极大的安全隐患,所以我们必须在正式启用之前尽早删除,或者设置一个比较安全的密码。
- 对于密码数据的存放,也不要存放在简单的文本文件之中,而应该使用专业密码管理软件来管理(如KeePass)。
- 安全参数。不需要使用的功能模块尽量都不要启用。例如,如果不需要使用用户自定义函数,就不要在启动的时候使用“--allow-suspicious-udfs”参数选项,以防止被别有居心的潜在威胁者利用此功能而对MySQL的安全造成威胁;
- 不需要从本地文件中Load数据到数据库中,就使用“--local-infile=0”禁用掉可以从客户端机器上Load文件到数据库中