mysql安全性系统是灵活的。它允许以许多不同的方法设置用户访问权限。通常,可通过grant 和revoke 语句来进行,这些语句对控制客户机访问的授权表进行修改。但是,您拥有的可能是不支持这些语句的旧版本mysql(这些语句在mysql3.22.11以前的版本 中没有使用),或者可能发觉用户的权限好像不是按希望地在工作。对于这样的情况,了解mysql授权表的结构以及服务器怎样使用它们来决定访问许可权是有帮助的。您了解到这样一个程度,就可以通过直接修改授权表来增加、删除或修改用户的权限,还可以在觳楸硎?br> 诊断权限的问题。 笔者假定您已经阅读了第11章的“用户账号管理”一节,并理解grant 和r e v o k e语句是怎样工作的。grant 和revoke 提供了建立mysql账号和相关权限的便利方法,但是,它们只是一个前端。所有真正的操作都发生在mysql授权表中。
mysql授权表的结构和内容
在网络上连接到服务器的客户机对mysql数据库的访问是由授权表内容控制的。这些表定位在mysql数据库中,并在首次安装mysql的过程中进行初始化(如附录a 所描述的)。表12-1和表12-2 示出列五个授权表,它们是user、db、host、tables_priv 和c o l um n s _ priv。
授权表的内容按下列各项使用: user user 表列出可连接到服务器和口令的用户,并指定用户拥有哪些全局(超级用户)权限(如果有的话)。任何在user 表项中所允许的权限都是全局权限,并适用于所有的数据库。例如,如果在这里允许delete 权限,则在该项中列出的用户可从任何表中删除记录。因此,在进行这项操作之前千万要小心,通常,最好在user 表项中关闭所有的权限,而使用其他的、有更多限制的表来指定权限。对超级用户(如r o o t)则是一个例外,但一般很少有。
db db 表列出数据库以及哪些用户拥有访问这些数据库的权限。这里指定的权限适用于数据库中所有的表。 host host 表与db表结合使用,在更细的级别上控制对特定主机的数据库访问权限。该表不受grant 和revoke 语句的影响,因此您会发现根本不会去使用它。 tables_priv tables_priv 表指定表级的权限。在这里所指定的权限适用于表中所有的列。 columns_priv columns_priv 表指定列级的权限。在这里所指定的权限适用于表中特定的列。 在12 . 2 . 4节“不用grant 建立用户”中,我们将讨论grant 语句怎样修改这些表以及怎样通过直接修改授权表来达到相同的结果。 tables_priv 和columns_priv 表是在mysql3.22.11中引入的(与grant 语句同时)。如果您拥有mysql的旧版本,则mysql数据库只有user、db 和host 表。如果已经从旧版本升级到3 . 2 2 . 11以上的版本,但仍没有tables_priv 和columns_priv 表的话,可运行mysql_fix_privileges_table 脚本创建它们。 没有rows_priv 表,因为mysql不提供记录级的权限。例如,不能使用户只局限于某些列中包含某个值的表中的那些行。如果需要这个能力,必须编写应用程序。如果要想执行咨询记录级锁定(advisory record-level locking),可用附录c 中介绍的get_lock() 函数进行。 授权表中包含两种类型的列:作用域列( scope column)和权限列( privilege column),前者决定一个项何时可用,后者决定一个项可授予哪些权限(有些授权表中包含其他各式各样的列,但在这里与我们无关)。 1. 授权表的作用域列 授权表作用域列指定表项何时使用。每个授权表项中都包含user 和host 列,以指明该项在特定的用户从特定的主机上连接时应用( host 表是一个例外,它被用于一种特定的、我们还未接触过的方法中)。其他表中包含附加的作用域列。例如, db 表中包含一个db 列以指明该项适用于哪个数据库。同样, tables_priv 和columns_priv 表中包含使该作用域对数据库中特定表或表中的列缩小的作用域字段。 2. 授权表权限列 授权表中还包含权限列。这些列指明了哪些权限被在作用域列中指定的用户所拥有。mysql支持的权限显示在以下列表中。该列表使用用于grant 语句的权限名。对于大部分来说,在user、db 和host 表中权限列的名字与在第11章利用grant 语句连接中所讨论的 权限名显然是相同的。例如, select_priv 列对应于select 权限。 3. 数据库和表的权限 以下权限适用于数据库和表的操作: alter 允许使用alter table 语句。这实际上是一个简单的一级权限,您必须根据打算用该表做什么以拥有其他的权限。 create 允许创建数据库和表。这个权限不允许您创建索引。 delete 允许从表中删除已有记录。 drop 允许删除数据库和表。不允许删除索引。 index 允许创建或删除表的索引。 insert 允许在表中插入新的记录。 references 未使用。 select 允许用select 语句从表中检索数据。该权限对于不含表的select 语句是不必要的,如select now() 或select 4/2。 update允许修改表中的已有记录。 4. 管理权限 下列权限适用于控制服务器操作或用户授权能力的管理操作: file 允许服务器在服务器主机上读写文件。该权限如果没有正当的原因则不应授予,如12 . 2 . 3节“授权表应避免的风险”中所述,它是危险的。服务器采取某些预防措施限定该权限在特定范围内使用。您只可以读世人都可读的文件,因此不需要考虑任何方式的保护。正在进行写操作的文件不允许是已经存在的。这可防止服务器覆盖重要的文件(如/ e t c / pass w d)或属于其他用户的数据库文件。例如,如果不施加该约束,您可能会替换全部mysql数据库中的授权表的内容。 如果您授予file 权限,就不能作为unix 的root 用户运行服务器,因为root 能在文件系统的任何地方创建新的文件。如果作为未授权用户运行服务器,则服务器只在该用户可访问到的目录中创建文件。 grant 允许将您所拥有的权限授予其他用户,其中包括grant 权限。 process 允许通过使用show processlist 语句或mysqladmin processlist 命令查询有关正在服务器中运行的线程信息。该权限还允许利用kill 语句或mysqla d m i n kill 命令取消线程。您始终可以查看或取消自己的线程。process 权限授予您对任何线程进行这些操作的能力。 reload 允许执行多种服务器管理操作。可发布flush sql 语句,还可执行mysqladmin 的命令r e l o a d、r e f r e s h、f l us h - host s、f l us h - l o g s、flush-privileges 和f l us h - tables。 通常这不是有危险的权限,尽管它实际上是管理权限。 shutdown 允许利用mysqladmin shutdown 关闭服务器。 在user、db 和host 表中,每个权限都作为单独的列给出。这些列都是用enum (“n”、“y”)类型声明的,因此所有权限的缺省值为“n” (o ff)。tables_priv 和columns_priv 表的权限由set 语句表示,它允许权限以使用单个列的任何组合形式指定。这两个表比其他三个表新,这就是为什么它们使用了更有效的表示方法的原因(user、db 和host 有可能在将来被改造,也通过set 来表示权限)。 table_priv 表的table_priv 列定义如下: set ('select ','insert','update','delete','create','drop','grant','references','index','alter') columns_priv 表的column_priv 列定义如下: set ('select ','insert','update','references') 列权限比表权限少得多,因为在列级中有意义的操作很少。例如,您可以创建一个表,但不能创建孤立的列。 user 表中包含对某些权限的列,这些权限不出现在任何其他授权表中,如f i l e _ priv、 p r o c e s s _ priv、reload_priv 和shutdown_priv 表。这些权限适用于执行与任何特定数据库或与表无关的服务器的操作。例如,使用户根据当前数据库关闭服务器是无意义的。
服务器如何控制客户机的访问
在使用mysql时,客户机访问控制有两个阶段。第一阶段发生在连接服务器时。服务器查找user 表看看是否能够找到与您的名字、正在连接的主机以及所提供的口令相匹配的项。如果不匹配,则不能连接。若匹配,则建立连接,然后继续进行第二阶段。在此阶段中,对于您发布的每个查询,服务器都会检查授权表以查看您是否具有充足的权限来执行该查询。第二阶段继续,直到关于该服务器的会话结束为止。 本节详细讨论mysql用来将授权表项与输入的客户机连接请求和与查询相匹配的规则。这包括在授权表作用域列中合法的值的类型、在授权表中权限信息组合的方法,以及表项检查的次序。 1. 作用域列的内容 某些作用域列需要直接量,但大多数列允许通配符或其他特殊的值。 host 该列值可以是一个主机名或一个ip 号。localhost 值的含义是本地主机,但仅当您实际使用localhost 主机值而不是使用该主机名连接时才进行匹配。假定本地主机名为p i t - v i per. s n a k e . n e t,且在user 表中有两个项,一个带有host 值或localhost,而另一个带有p i t - v i per.snake.net 值。带有localhost 值的项仅当您连接到localhost 时才进行匹配。另一个项仅当您连接到p i t - v i per.snake.net 时才匹配。如果想让用户能够以任意一种方式进行连接,在user 表中需要两个项。还可以用通配符指定host 的值。sql的模式字符‘ %’和‘ _’也可以使用,并与查询中的like 操作符有相同的含义。sql 模式(不允许regex 模式)字符可用于名字和ip 号。例如,%.wise.edu将与wise.edu域中的任何主机相配。同样, 192.168.% 与192.168 类b 子网中的任何主机相配,因而192.168.3.% 与192.168.3 类c 子网中的任何主机相配。 值‘%’总是与任何主机相配,用于允许某个用户从任何地方进行连接。空白的h o s t的值与‘%’含义相同(例外:在db 表中,空白的host 值含义是“检查host 表的进一步的信息”。 自mysql3.23 以来,还可以利用表明网络号的二进制位数的网络掩码指定ip 号。例如, 192.168.128.0/17 指定17 位的网络号并用ip 地址的前17 位中的19 2 . 16 8 . 12 8与所有主机相配。 user 用户名必须或者是直接量或者是空白的(空)。空白值与任何用户相配。作为user 值的% 不意味着空白,相反,它与带有% 的直接名字的用户相配,这可能不是您想要的东西。 当一个输入的连接对应user 表进行检查并且相配的项包含一个空白的user 值时,客户机被认为是匿名用户。 password 口令值或者是空白的(空)或者是非空的,但不允许通配符。空白的口令并不是说与任何口令相配。它的意思是用户不必指定口令。口令以加密值而不是以直接值被存储。如果在password 列存储了一个直接的口令,则该用户将不能连接!grant 语句和mysqladmin password 命令自动对口令进行加密,但是,如果使用像insert、replace、update或set password 这样的语句,则必须用pa s s w o r d (“n e w _ pa s s w o r d”) 而不是简单地用“n e w _ pass w o r d” 来指定口令。 db 在columns_priv 和tables_priv 表中,db 值必须是直接的数据库名,不允许模式和空白名。在dn 和host 表中,db 值可以用直接量指定,或通过使用指定通配符的sql 模式字符‘%’或‘_’来指定。% 值或空白将与任何数据库相配。 ta b l e _ name、column_name 这些列中的值必须是直接量的表名或列名,模式值和空白是不允许的。 有些作用域列由服务器视为区分大小写的,其他的则不是。这些规则已在表12 - 3中总结。特别注意, table_name 值始终是区分大小写的,即使查询中的表名处理是根据服务器运行的文件系统,其大小写敏感性也是如此(在unix 中区分大小写,而在windows 中不区分大小写)。
2. 查询访问检查 每当您发布查询时,服务器都要检查您是否有足够的权限来执行查询。它是通过依次检查user、db、table_priv 和columns_priv 表来进行的,直到它确定您有适当的访问权或徒然地搜索了所有的表为止。更准确地说: 1) 在开头进行连接以查看所拥有的全局权限时,服务器检查相匹配的user 表项。如果有全局权限且满足该查询,则服务器执行查询。 2) 如果全局权限不满足,则服务器在db 表中寻找一个项,并将该项的权限增加到您的全局权限中。若结果满足该查询,则服务器执行查询。 3) 如果全局和数据库级权限的组合不够,服务器将保持不断地查找,首先在tables _ priv 表中,然后在columns_priv 表中查找。 4) 如果在所有表检查后发现您没有许可权,服务器将拒绝执行查询。 口令是怎样存储在user 表中的口令在user 表中以加密串的形式出现,因此您不知道用户的口令是什么,即使已经拥有对该表的访问权也是如此。通常,似乎认为pa s s w o r d()函数执行与unix 口令所使用的类型相同的加密方法,其实并不是这样的。 当两种加密方法是单向的且不可逆时它们是相同的,但是, mysql不使用与unix 相同的加密算法。这意味着,即使您使用unix 口令作为mysql的口令,也别希望加密的口令串相配。如果想对应用程序使用unix 的加密方法,应使用c rypt() 函数而非pa s s w o r d ( )。 在逻辑条件下,授权表由服务器按以下形式使用: user or db or tables_priv or columns_priv 您可能正在奇怪,为什么上面的描述只涉及到五个授权表中的四个。正击中要害,服务器实际检查访问许可权的方法如下: user or (db and host) or tables_priv or columns_priv 笔者先给出了较简单的表达式,因为host 表不受grant 或revoke 语句的影响。如果一直保持使用grant 和revoke 来管理用户权限,将不需要考虑host 表。但下面还是给出了对该表的讨论,以便您有所了解: 当服务器检查数据库级的权限时,它查看客户机的db 表的项。如果host 列值是空白,其含义是“检查host 表查找哪些服务器可访问该数据库”。 服务器利用与来自db 表项相同的db 列为项查看host 表。如果host 表项与客户机主机不相配,就不授予数据库级权限。如果这些项都与客户机正在连接的主机的host 列值相配,则db 表的项和host 表的项进行组合以产生客户机的数据库级权限。 但是,权限用逻辑and 进行组合,其意思是,除非客户机同时出现在两个项中,否则它没有指定的权限。这样,可以在此db 表项中授予一组基本的权限,然后用host表项有选择地对特定的主机禁止使用这些权限。例如,允许从域的所有主机中访问某个数据库,但是对于定位在非安全范围内的主机禁止使用数据库权限。 以上描述无疑要进行访问检查,这听起来好像是相当复杂的过程,尤其是考虑到服务器要为客户机发布的每一个单独的查询检查权限时。但是,该过程相当地快,因为服务器不对每个查询实际检查授权表的信息。相反,在服务器启动时,它将该表的内容读到内存中,然 后用内存中(i n - m e m o r y)的拷贝来检验查询。这对于访问检查操作性能产生了推进,但有相当重要的负面效应:如果您直接修改了授权表的内容,则服务器将不会注意到权限的改变。 例如,如果通过用insert 语句增加一个新记录到user 表中来增加了一个用户,在该项中指定的该用户将不能连接到该服务器。这是迷惑新管理员的问题(有时比较有经验的管理员也是如此),但是解决方案很简单:告诉服务器在您修改授权表后重新加载其内容即可。可以通过发布flush privileges 语句或通过执行mysqladmin flush-privileges (或mysqladmin reload,如果是不支持flush-privileges 的旧版本的话)来进行。 3. 作用域列匹配顺序 mysql服务器以一种特定的方式对授权表的项进行排序,然后试着通过按顺序查看所有的项来匹配输入的连接。所发现的第一个匹配确定被使用的项。重要的是要理解mysql使用的排序顺序,尤其是对于user 表的排序。这好象是在为难试图理解mysql安全性的许多 人们。 当服务器读取user 表的内容时,它根据host 和user 列的值对项进行排序。host 值是域值(带有相同host 值的项被一起存放,然后按照user 值进行排序)。但是,排序不是词典式的,或不完全是这样的。要记住的原则是直接值比模式值优先。这意味着如果正从 boa.snake.net 中进行连接并且带有boa.snake.net 和%.snake.net 的host 值,则直接值将是首选的。同样, %.snake.net 优先于% . n e t,如此类推,%.net 优先于%。ip 号也以这种方法进行匹配。对于从带有192.168.3.14 的ip 号的主机中连接的客户机,具有19 2 . 16 8 . 3 . 4、19 2 . 16 8 . 3 . %、19 2 . 16 8 . %、192.% 和% 的host 值将按此顺序匹配。
授权表应避免的风险
本节讨论在授权时应遵守的某些预防措施以及维护人员作出轻率选择所冒的风险。通常,给用户授超级用户权限应保守。也就是说,不要在user 表中放置权限。取而代之的是使用其他的授权表来限制用户对特定数据库、表或列的权限。user 表中的权限可令用户影响服务器的操作或访问任何数据库中的任一表。 不要对mysql数据库授权。该数据库连接授权表,在该数据库上具有权限的用户可以修改这些表来获得任何其他数据库的权限。允许用户修改mysql数据库表的授权还能给该用户授予一个全局grant 权限。如果该用户可直接修改表,就相当于可以发布任何的grant 语句。 file 特权相当危险,不要轻易授权。以下是一个file 特权用户的例子: create table etc_passwd(pwd_entry text); load data infile "/etc/passwd" into table etc_passwd; select * from etc_passwd; 发布这些语句后,该用户拥有您的口令文件的内容。事实上,在服务器中,任何公用的可读文件的内容都可以通过网络由任何有file 权限的用户访问。 file 权限也可以用来对系统中的、不能用足够的限制文件许可权来建立的数据库进行折衷。这就是为什么应该将数据目录的内容设置成为只能由服务器可读的原因。如果与数据库表相对应的文件是全球范围内可读的,则不仅有该服务器主机账号的用户可读取它们,而且有file 权限的任何客户机也都可通过网络连接并读取它们!下面的过程说明如何进行此项操作: 创建有longblob 列的表: use test create table tmp (b longblob) 用此表读取您要窃取的表的每个文件的内容,然后将该表的内容写到您自己的数据库文件中:
现在,您有了一个新表y,它包含other_db.x 的内容,您可以对它进行完全访问。 为了避免某人用同样的方式攻击您,应根据“内部安全性:安全数据目录访问”的指导设置数据目录的许可权。当启动服务器以限制用户利用show database 和s h o w table s对他们不能访问的数据库进行访问时,还可使用-skip-show-database 选项。这将有助于防止用户查找不应访问的数据库和表。 alter 权限可能以您没有设想的任何方法被使用。假定您要user1能访问table1但不能访问t a b l e 2。但带有alter 权限的用户可能会通过使用alter table 将table2 重新命名为table1来打乱您的这一设想。 要小心使用grant 权限。如果两个用户有不同的权限但都具有grant权限,则他们能使互相的访问权更强大。
不用grant 建立用户
如果是mysql3.22.11以前的版本,则不能用grant(或r e v o k e)语句建立用户和访问权限,但是可以直接修改授权表的内容。如果理解grant 语句是怎样修改授权语句的,这个问题就很容易理解。可以通过手工发布insert 语句自己来进行( insert 语句很难正确输入,但这是另外的问题。事实上这个问题就是为什么grant 语句设计得如此易于使用的原因)。 当发布grant 语句时,指定了用户名和主机号,可能还指定了口令。user 表项对该用户建立,这些值记录在该项的u ser、host 和password 列中。需要仔细考虑的一件事是,grant 语句对口令进行加密,而insert 则不能,它需要用password() 函数对insert语句中的口令进行加密。 如果已经指定了数据库级的权限,用户名和主机名将记录在db 表项的user 和host 列中。已授权的数据库记录在db 列中,所授予的权限记录在权限列中。 对于表级和列级权限,效果是类似的。在tables_priv 和columns_priv 表中创建项来记录用户名、主机名和数据库以及需要的表或者表和列。授予的权限记录在权限列中。 如果还记得上述讨论,即使不用grant 语句也能够进行grant 的工作。请记住,当直接修改授权表时,需要告诉服务器重新加载授权表,否则它将不会留意这些变化。可以通过执行mysqladmin flush-privileges 或mysqladmin reload 命令施加重新加载操作。如果忘记 这步操作,服务器将不按您的设想进行工作。 以下grant 语句创建有全部权限的超级用户,其中包括向其他用户授权的能力: grant all on *.* to [email protected] identfiedby "coffee" with grant option 该语句将创建user 表的[email protected] 的项,并开启所有权限,因为超级(全局)用户将存储在那里。用insert 进行同样工作的语句是:
这是一个很难看的insert 语句!您甚至会发现它在您的mysql版本上不工作。授权表的结构可能已经改变,在user 表中可能不会有14 个权限列。此时,应使用s h o w columns 来查找每个授权表包含些什么权限列,然后调整insert 语句。 下列grant 语句还创建具有超级用户状态的用户,但是只对单个的权限: grant all on *.* to [email protected] identfiedby "flushpass" 您可能还记得我们曾在第11章建立flush 用户时使用过该语句。本例中的insert 语句比前一个更简单,可以很容易地列出列名并指定唯一的一个权限列。所有其他的列都将设置为缺省值“n”:
数据库级的权限是用on db_name.* 子句而不是用on *.* 授予的: grant all on samp_db.* to [email protected] identfiedby "ruby" 这些权限都不是全局的,因此不能存储在user 表中。我们仍需要在user 表中创建一个项(使得用户可以连接),但我们还需要创建一个db 项来记录数据库级的权限:
“n” 列是针对grant 权限的,对于在末尾带with grant option 的数据库级的grant 语句,应该将该列设置为“y”。 为了设置表级或列级权限,对tables_priv 或columns_priv 表使用insert 语句。当然,如果没有grant 语句,也将没有这些表,因为它们都是在同一时间出现在mysql版本中的。如果有这些表但由于某种原因想手工操纵它们,那么您不能使用单个的列使权限有效。您或者设置tables _ priv. table_priv 或者设置c o l um n s _ priv.column_priv 为一个set 值,该set 值由您允许的权限组成。例如,为了允许对某个表的select 和insert 权限,应在相对的tables_priv 项中设置table_priv 列为“select, insert” 值。 如果要想修改某个用户的权限(该用户的mysql账号已经存在),应使用update而非insert。无论是增加还是取消权限都是这样。为了删除一个完整的用户,使用delete 从该用户所在的每个授权表中删除其项。 如果要想避免发布查询来直接修改授权表,应看一看与mysql分发包一起的mysqla c c e ss和mysql_setpermissions 脚本。 一个权限难题,第二部分 在第11章的“一个权限难题,第一部分”中,我们看到了一种授权情况,在这种情况中权限没有预想的效果。我们来简单地叙述一下问题,经常与新的mysql安装一起出现的一个问题是管理员想要为用户增加一项,使用户们可从几个主机中进行连接。这样做最直接的方法是使用包含‘ %’通配符的主机名说明符,因此管理员用类似以下的语句创建一个用户: grant all on samp_db.* to [email protected]%.snake.net identfiedby "cocoa" fred 碰巧有一个服务器主机的账号,因此他试图从这里进行连接:
为什么会这样?为了继续理解,您必须同时考虑mysql_install_db 是怎样建立初始授权表和服务器怎样使用user 表的项匹配客户机连接的。当通过运行mysql_ i n s t a l l _ db对数据库初始化时,它创建带有如下host 和user 值的user 表的项:
前两项通过指定localhost 或主机名允许r o o t连接到本地主机。后两项允许来自本地主机的匿名连接。当为fred 增加此项时,则user 表将包含这些项:
带localhost 的两项一起存储,有root 的那个项放在前,因为这项比空白的更具体、更详细。带有p i t - v i per.snake.net 的项以类似的方法一起存储。这些项都是不带任何通配符的直接host 值,因此它们都优先于fred 的项进行存储,而fred 项使用了一个通配符。特别地,匿名用户项在存储顺序上优先于fred 的项。 此结果是,当fred 试图从本地主机上进行连接时,带有空白用户名的项匹配先于在host 列中包含%.snake.net 的项。该项的口令是空白的,因为缺省的匿名用户没有口令。由于fred 在连接时指定了口令,则产生一个错误匹配和错误连接。 这里要记住的事情是,尽管使用通配符指定用户可连接的主机这种方式非常方便,但只要您在user 表中保留匿名用户,就可能会出现从本地主机中连接的问题。 通常,笔者建议删除匿名用户项。这将使您的工作更轻松一些: mysql> delete from user where user=""; 如果想更彻底的话,可将其他授权表的匿名用户项也删除。有user 列的表是db、tables_priv 和c o l um n s _ priv。 本节给出的难题是一种特殊的情况,但包含了更为普遍的内容。如果某个用户的权限不按所希望的方式工作,则检查授权表,看看是否有某些项包含了比所讨论的该用户的项更详细的host 值,以及试图通过那个用户匹配连接的host 值。如果是这样的,问题的原因可能就是它。您可能需要使该用户的项更详细(或增加另一个项来包括更详细的情况)。