【51CTO.com独家特稿】本文将为读者详细介绍MySQL数据库在体系结构方面的安全问题以及相应的攻击方法,在后续的文章中我们将介绍相应的防御措施。
一、MySQL支持的机器体系结构
MySQL自称是当今世界上最流行的开源数据库,并且是免费发行,同时还能运行在各种平台之上——这也正是黑客们对它趋之若鹜的原因。与其它大型数据库相比较而言,它的配置工作要容易、简单得多,并且性能也说的过去。尽管它的用法相对来说简单一些,但是在安全配置方面还是有大量的工作要做的,这也是许多数据库存在安全隐患的原因之一。
一般情况下,人们都是通过二进制文件程序包来安装MySQL数据库服务器,这时它可以安装到如下所示的系统:Linux x86、Linux IA64、Linux AMD64、Windows、Solaris、FreeBSD、Mac OS X、HP-UX、IBM AIX、QNX、Novell Netware、OpenBSD、SGI IRIX、DEC OSF;如果从源代码进行安装的话,那么它能支持的平台还要多。
#p#
二、MySQL的部署
由于MySQL服务器是如此流行,以至于在网络中的任何角落几乎都能找到它的身影,实际上,它不仅安装到专用服务器中,就连桌面机器中也常见它的踪迹。
在一般的配置中,客户端会通过TCP 3306端口连接到MySQL。对于Windows平台而言,还可以通过有名管道使用MySQL,不过一般不推荐这样配置。默认情况下,以有名管道模式运行的MySQL会同时侦听TCP 3306端口和一个名为MySQL的有名管道。与诸如Oracle之类的数据库系统相比,MySQL使用的网络协议就比较简单了,并且默认情况下是使用明文通信,当然也可以使用SSL来保护通信。启用SSL协议后,数据库仍然是一TCP 3306端口。
您可以很轻松地检测一台主机上运行的MySQL的版本,因为只要连接该机器,它就会返回数据库的主要版本号和次要版本号,某些版本还会提供操作系统的提示信息。任何能够抓取旗标的TCP端口扫描器都能够返回MySQL版本。
MySQL最普遍的应用是为动态web应用程序提供后端支持,当我们扫描网络时见到的MySQL通常出现在Apache/PHP应用程序的后端,甚至有时候它竟跟web服务器运行在同一台主机上。在一些大型组织中,它常用作日志记录服务器,用于存放入侵检测系统日志、web日志或者其他审计任务。有些情况下,特别是在开发环境中,MySQL经常被安装到一台桌面机器上,所以在PC上扫描到它也就不足为奇了。
由于MySQL通信协议是明文通信,因此人们喜欢在MySQL服务器所在主机上同时安装一个SSH服务器,并使用端口转发技术通过加密隧道连接至3306端口。这种方法有几种好处:数据在运输中是加密的,并强迫执行一个额外的认证步骤,同时还给该数据库的连接提供了额外的审计记录。
此外,有人认为MySQL服务器应该与web服务器安装到同一台机器上,因为这样可以避免远程连接,但是这种配置本身就是不安全的。之所以这样说,是因为MySQL的数据表是以文件的形式存储的,并且通常没有锁定,那么一旦Web应用程序出现文件泄露漏洞,这就使得攻击者能够下载数据库的所有内容。从另一方面来说,Web应用程序中的SQL注入漏洞也可能导致攻击者得以修改web服务器上的脚本内容。恰当的文件权限可以防止此类问题,但是除此之外,将web服务器和数据库服务器置于同一台机器上还为攻击者敞开了许多其它的方便之门。
#p#
三、WinMySQLAdmin简介
当MySQL安装到Windows平台的时候,通常会提供WinMySQLAdmin工具。当这个工具第一次运行时,它会将自身添加到运行它的用户所在的启动组。当它运行时,WinMySQLAdmin会自动地启动MySQL,这会导致运行于Windows主机的MySQL的实例在无意间被运行。
此外,当WinMySQLAdmin运行于没有默认MySQL用户帐户的主机时,它会要求用户创建一个用户名和口令对,并将这些凭证以明文形式存储在系统根目录(例如,c:\winnt)下的my.ini文件中。这个文件通常对该主机上的任何用户来说都是可读的。
#p#
四、缺省用户名和口令
MySQL的默认配置随平台、部署模式、发行版本(源代码或者二进制文件)和初始配置的不同而异,在有些情况下MySQL服务器一经安装就可能被远程攻击者所攻陷——如果缺省配置有问题的话。举例来说,MySQL 4.0.20的一些默认配置下,表mysql.user中有四条缺省记录,有两条用于root帐户,另两条用于匿名帐户。并且,有一个带有root权限的远程表项是给主机build上的根用户的。这些表中的表项的具体含义,我们会在后面加以详解,现在我们只需知道的是:
如果您位于本地主机,那么您可以作为密码为空的root根用户通过身份验证,并获得数据库的完全控制权。如果您位于本地主机,您可以使用任何用户名通过身份验证,并获得对该数据库的来宾访问权限。如果您位于一个远程主机上,同时能够控制服务器的名称解析,这样的话就可以使你的主机名看起来是“build”,所以您就可以作为密码为空的root用户通过身份验证,从而获得数据库的完全控制权。如果您位于一台名为build的远程主机上,那么您就可以使用任何用户名来通过身份验证,并获得数据库的来宾访问权限。
在Windows主机上,根帐户的存在会导致任何本地用户都能将自身权限提升为本地系统级访问权限;而在默认情形下,MySQL就是运行在系统权限级别的。糟糕的是,攻击者只需简单将其机器命名为build,那么他就轻而易举地获得了对运行MySQL服务的机器的远程系统级访问权限。当然,攻击者必须位于目标所在的同一个NetBIOS名字域中,或者能够伪造一个DNS响应。针对这一问题的防御方法是:
- 安装MySQL时禁用网络连接。
- 安装之后立即删除mysql.user表中除本地主机root帐户之外的全部帐户。
- 为本地主机root帐户设置一个复杂的密码。
#p#
五、身份验证协议中的安全漏洞
MySQL使用一个专有协议进行身份验证和发送接收数据。这个协议比较简单,所以可以为MySQL轻松编写一个定制的客户端。换句话说,MySQL身份验证协议的各版本中的严重漏洞可能会导致服务器立即被攻陷。下面我们介绍之前发现的一些漏洞,以及针对这些漏洞的攻击方法。
描述这些攻击方法之前,我们先来大致介绍涉及身份验证协议的包格式和加密机制。当客户端连接服务器时,服务器问候数据包,其中包含下列域:
- Packet Length (3 bytes)
- Packet Number (1 byte)
- Protocol Version (1 byte)
- Server Version String (以null结尾)
- Server Thread ID (4 bytes)
- Challenge String (以null结尾)
- Server Capabilities Flags (2 bytes)
- Server Character Set (1 byte)
- Server Status (2 bytes)
- Padding (数据包的其余部分)
就身份验证协议而言,有关的内容是Protocol Version和Challenge域,但是Server Version String域则对确定服务器容易受到哪些身份验证漏洞的攻击非常有帮助。客户端然后会发送一个身份验证数据包给服务器:
- Packet Length (3 bytes)
- Packet Number (1 byte)
- Client Capabilities (2 bytes)
- Max packet size (3 bytes)
- Username (以null结尾)
- Password (challenge响应以null结尾)
下面我们对身份验证协议中的安全漏洞进行介绍。MySQL身份验证协议中已经发现过许多安全漏洞,我们这里略举一二。
#p#
身份验证协议4.1版本之前的基本密码弱点
4.1版本之前的MySQL,无需知道密码,只要知道密码的hash值(包含在mysql.user表中)就能通过身份验证——这意味着,攻击者根本无需编写密码杂凑值的破解程序,因为修改标准MySQL客户端让它接受密码杂凑而非密码要容易多了。当然,用户倾向于多处使用同一个密码,尤其是root密码,所以破解任何一个密码的杂凑值,就极有可能在多个地方派上用场。
3.23.11版本之前的身份验证算法
3.23.11版本之前的MySQL的身份验证机制中有一个严重的缺陷,即攻击者仅仅使用杂凑后的密码的单个字符就能通过身份验证。其实杂凑后的字符串由32个字符中的某些字符构成的,所以攻击者只需进行少量猜测就能登录。
3.23.54版本之前的CHANGE_USER
对于3.23.54版本之前的MySQL来说,如果用户可以进行身份验证,它就有机会提交一个超长字符串(来触发缓冲区溢出)或者单字节字符串的CHANGE_USER命令来提权。
4.1.1、4.1.2和5.0.0版本中的身份验证算法
通过提交一个精心制作的身份验证数据分组,对于攻击者而言,就有可能绕过在4.1.0到4.1.2以及5.0早期构建版的MySQL中的密码身份验证机制。下面的代码取自sql_parse.cpp文件的check_connection代码:
/*
旧版本的客户端发送以null结束的字符串作为密码;新客户端使用尺寸(1字节)+字符串(并非以null作为终止符)作为密码。 因此如果是空密码的话,两者都会发送'\0'。
*/
uint passwd_len= thd->client_capabilities & CLIENT_SECURE_CONNECTION ?
*passwd++ : strlen(passwd);
0x8000是在客户端的能力标志中规定的,用户可以指定所选的passwd_len字段。对于该攻击,我们选择0x14 (20),这是我们预期的SHA1杂凑长度。
现在要执行多道检测以保证用户是从授权连接的主机上进行身份验证。通过这些检测后,我们到达:
/* 检查密码:它应该为空或者是正确的 */
if (passwd_len == acl_user_tmp->salt_len)
{
if (acl_user_tmp->salt_len == 0 ||
acl_user_tmp->salt_len == SCRAMBLE_LENGTH &&
check_scramble(passwd, thd->scramble, acl_user_tmp->salt) == 0 ||
check_scramble_323(passwd, thd->scramble,
(ulong *) acl_user_tmp->salt) == 0)
{
acl_user= acl_user_tmp;
res= 0;
}
}
Check_scramble函数失败,但是在check_scramble_323函数之内我们看到:
my_bool
check_scramble_323(const char *scrambled, const char *message,
ulong *hash_pass)
{
struct rand_struct rand_st;
ulong hash_message[2];
char buff[16],*to,extra;
const char *pos;
hash_password(hash_message, message, SCRAMBLE_LENGTH_323);
randominit(&rand_st,hash_pass[0] ^ hash_message[0],
hash_pass[1] ^ hash_message[1]);
to=buff;
for (pos=scrambled ; *pos ; pos++)
*to++=(char) (floor(my_rnd(&rand_st)*31)+64);
extra=(char) (floor(my_rnd(&rand_st)*31));
to=buff;
while (*scrambled)
{
if (*scrambled++ != (char) (*to++ ^ extra))
return 1; /* 密码错误 */
}
return 0;
}
现在,用户已经规定了一个他想要的杂凑字符串,本例为一个零长度的字符串。最后的循环对杂凑字符串与MySQL已知正确响应字符串进行逐字符比较,直到杂凑字符串中的所有字符都比较完为止。由于在杂凑字符串中根本没有要比对的字符,所以函数会立即返回0,即允许用户使用零长度字符串通过身份验证。
这个缺陷的利用方法相当简单,但是我们还是必须要编写一个定制的MySQL客户端才行。除了使用零长度字符串绕过身份验证外,还可以使用长杂凑字符串来溢出基于堆栈的缓冲区。我们可以使用伪随机数发生器my_rnd()函数输出的字符来溢出该缓冲区。字符的范围介于0x40到0x5f之间。对于一些平台来说,执行任意代码是可能的,但是该漏洞利用非常复杂,并且需要进行暴力破解,或者知道一个密码杂凑值。
为了进行这些攻击,攻击者必须知道或者能猜出用户的名称,所以重命名MySQL的root帐户不失为一个有效的预防措施。此外,有关账户还必须能够从攻击者的主机进行访问,所以应用基于IP地址的登录限制也能减轻这些漏洞所带来的危害。
#p#
六、利用体系结构设计缺陷
下面,我们将介绍设计缺陷有哪些?如何利用这些缺陷?以及如何识别和抵御这些攻击。
对于MySQL,历史上曾经出现过多个设计缺陷,它们主要影响身份验证协议,这些我们在上文中已经介绍过了。下面,我们将从更通用的、体系结构的角度来考察在MySQL中的各种安全弱点。
认证机制中的各种漏洞允许远程用户无需任何凭证就可以通过身份认证,这些应该属于体系结构漏洞中最严重的一类。
我们知道,凡事都有其两面性,MySQL的简单性虽然给我们带来了便利,但是它同时也成为了其最大的弱点。举例来说,Microsoft SQL Server的一个极其有用的特性就是能够在远程数据库服务器上执行查询,比如我们可向服务器发送一个如下所示的查询:
SQL Server中的OpenRowset语句允许您在SQL Server查询过程中向另一个(运行不同的数据库管理系统的)服务器提交查询。但是这个特性很容易被滥用。据我们所知,最常用的滥用方式之一是可以通过它来扫描该SQL Server所在网络的端口,因为它可以利用不同长度的响应时间来确定远程主机是否存在,是否为SQL Server。
而对于MySQL来说,由于没有对应的OpenRowset语句,因此MySQL也就不会遭受此类攻击了。可问题是,如果软件的行为太简单,那么就可能缺少针对滥用的保护措施。
#p#
七、利用用户定义的函数
几乎每个数据库管理系统都提供了调用定制的本机代码的机制,例如SQL Server有扩展存储过程的概念,Oracle有外部存储过错的概念,等等。虽然这些机制的名称不同,但是基本原理是一致的,那就是用户创建一个可动态装载程序库(在windows上为.dll;在linux中为共享对象.so),然后数据库可以通过SQL语句来调用这个库。
由于大多数数据库运行在管理权限上,或者至少能控制他们自己代码和数据,因此这就会招致一个严重的安全问题。如果一个MySQL用户可以创建和执行一个恶意UDF,那么整个数据库服务器的安全性就会岌岌可危。
触发添加和使用UDF的过程的内容,我们前面已经讲过一些了,这里将深入探讨更多的细节,因为一旦MySQL本身被攻陷,那么也会殃及MySQL所在的主机,而有时候利用恶意UDF攻陷MySQL是件非常简单的事情。
MySQL提供了一个机制,通过该机制可以扩展默认函数集,方法是编写定制的包含用户定义的函数或者UDFs的动态库。这个机制可以通过CREATE FUNCTION语句加以访问,而MySQL.func表中的表项则可以手动添加。包含函数的程序库必须能够从MySQL装入动态库时所用的路径加以访问。
#p#
攻击者滥用这个机制的通常方式是,创建一个恶意程序库,然后使用SELECT . . . INTO OUTFILE将其放到一个适当的目录中。该程序库就位之后,攻击者需要更新或者插入mysql.func表,以便配置MySQL使其加载该程序库并执行该函数。一个UDF程序库示例源代码如下所示:
#include
#include
/*
可以使用下列编译器编译该代码:
gcc -g -c so_system.c
然后执行如下所示命令:
gcc -g -shared -W1,-soname,so_system.so.0 -o so_system.so.0.0 so_system.o -lc
*/
enum Item_result {STRING_RESULT, REAL_RESULT, INT_RESULT, ROW_RESULT};
typedef struct st_udf_args
{
unsigned int arg_count; /* 参数数目 */
enum Item_result *arg_type; /* 指向item_results的指针 */
char **args; /* 参数指针 */
unsigned long *lengths; /* 字符串参数的长度 */
char *maybe_null; /* 对于所有的maybe_null参数,都设为1 */
} UDF_ARGS;
typedef struct st_udf_init
{
char maybe_null; /* 如果函数返回NULL,则为1 */
unsigned int decimals; /* 用于实函数 */
unsigned long max_length; /* 用于串函数 */
char *ptr; /* 函数数据指针 */
char const_item; /* 如果结果与参数无关,则为0 */
} UDF_INIT;
int do_system( UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error)
{
if( args->arg_count != 1 )
return 0;
system( args->args[0] );
return 0;
}
函数可以通过如下所示的命令来添加至MySQL:
然后,mysql.func表看上去会是这样的(您还可以通过手动方式更新):
于是,我们就可以像下面这样来调用函数了:
即使攻击者受文件权限的制约无法在目标系统上创建自己所有的程序库,但是他仍然可以通过一个现有的函数来达到不可告人的目的。攻击者的难点在于大多数函数的参数表不可能匹配MySQL UDF的原型:
int xxx( UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error)
当然,机智的攻击者可能会通过调用一个当解释传给MySQL的参数时会出现某种可控故障的程序库来设法执行任意的代码。然而,现有的系统程序库中的函数仍然可能会做坏事——举例来说,在Windows系统中将ExitProcess作为MySQL UDF进行调用,这会导致MySQL立即退出——即使发起调用的用户不具备Shutdown_priv权限:
您还可以锁住当前登录用户的工作站,只要使用下列命令,其效果跟按下CTRL-ALT-DEL一样:
然后工作站就会被锁定。
总之,在MySQL中的UDF机制对于开发人员来说是极其灵活和有用的,但是也给攻击者带来了莫大的帮助。所以,防御此类攻击的最好措施就是,仔细分配MySQL权限(特别是MySQL数据库和mysql.func表有关的权限)、文件权限以及限制使用SELECT . . . INTO OUTFILE。
#p#
八、利用访问控制系统中的漏洞
由于4.1.x版本之前版本没有实现视图,因此也就没有记录级别的安全机制。对于某些用户来说,这可能是一个问题,并且在有些情况下缺少可用的安全配置,所以它们最好选择更高版本。举例来说,假设一个数据仓储系统使用MySQL用户来确定哪些用户可以执行哪些动作,该用户通常想要做的事情之一就是更改他们的口令,所以该软件使用了一个表单查询实现了该功能:
现在假设入库脚本存在被可以替换
#p#
九、利用缺乏的安全特性
MySQL没有内置访问违例审计机制,但是它支持所有连接和查询的完整日志记录。 这对于安全性的影响是显而易见的,在一些情况下,缺乏本地审计时一个非常大的安全问题。然而,由于提供了对调试日志记录的支持,使得MySQL能够通过--log选项记录每一连接和语句到日志文件。
前面提到的MySQL存储引擎大部分都不支持引用完整性或者事务。虽然这些特性是可用的,但是默认的存储引擎MyISAM却没有实现这些特性。 因此,下面的讨论只是针对MySQL的默认行为的,当然,我们这里讨论的某些问题也可能出现在那些功能不是很丰富的数据库管理系统中。
在一些缺乏引用完整性的应用程序中,可能会出现竞态条件从而引起安全问题,举例来说,假设我们的应用程序通过一个用户表来执行其安全模型:
create table users( username varchar(200), password varchar( 200 ), userid int );
insert into users values ( 'admin', 'iamroot', 0 );
insert into users values ( 'fred', 'sesame', 1 );
insert into users values ( 'joe', 'joe', 2 );
这些用户有权访问一些资源,并使用编号进行标识:
create table resources( name varchar( 200 ), resourceid int );
insert into resources values( 'printer', 1 );
insert into resources values( 'filesystem', 2 );
insert into resources values( 'network', 3 );
这些资源的访问权限是通过一个访问控制表来实现的,表中的记录决定用户是否可以访问各个资源:
create table accesscontrol( userid int, resourceid int, allowed int );
# Admin可以访问下面三种资源:
insert into accesscontrol values ( 0, 1, 1 );
insert into accesscontrol values ( 0, 2, 1 );
insert into accesscontrol values ( 0, 3, 1 );
# Fred可以访问文件系统和网络:
insert into accesscontrol values ( 1, 2, 1 );
insert into accesscontrol values ( 1, 3, 1 );
#Joe只能访问打印机
insert into accesscontrol values ( 2, 1, 1 );
#p#
假设我们没有强制执行引用完整性,如果我们利用id 2删除用户joe,如下所示:
表accesscontrol中与joe有关的的所有记录仍然在那里。如果我们借助下一个可用的id(即2)添加另一个用户的话,那么这个用户就会继承joe在accesscontrol表中的记录。
对于提供引用完整性的数据库来说,我们就可以规定该用户标识为accesscontrol表中的外键,这样,当该用户的记录删除时,那么在accesscontrol中对应的所有记录也会自动删除。
然而,在默认情形下缺乏事务支持也可能会导致安全隐患。举例来说,假设实现前面的系统的公司具有审计每次密码改变的要求, 同时假定使用了如下所示的查询:
假定这两次查询之间的服务器连接失败。用户的密码将已经更改,但是系统尚未进行审计。如果我们正在使用非默认的、支持事务的MySQL存储引擎,那么我们就可以在第一个语句之前开始事务,然后在第二个语句之后提交事务,那么密码改变就不会漏掉审计了。
在4.0版本之前,MySQL并不支持UNION语句。由于SQL注入是最常见的数据库攻击形式之一,而UNION又是攻击者的指令表中的关键部分,因此在防御SQL注入攻击方面,通常认为4.0版之前的MySQL要比其他数据库系统更加安全。然而,应用开发人员却对这一局限性颇为不满;此外,如果您运行版本较老的MySQL的话,那就会受到目前已经修复的安全漏洞的威胁。所以,如果有一个功能受限同时又补丁齐全的MySQL就好了,但是看起来这是不可能的。如果读者有志于此的话,抽时间您可以亲自动手试试。
此外,4.1版本之前的MySQL并不支持子查询,下面是一个子查询的例子:
由于进行SQL注入攻击时,攻击者通常能控制查询字符串的部分字符,所以如果缺乏子查询的话,那么从安全的角度来说倒是一个优点。然而,现实中如果缺乏某些特性的话,MySQL是很难推荐给管理层的,更别说开发团队了。“缺乏功能”有时候也是有好处的,例如Oracle没有十分详细的错误信息,但是对于SQL Server 来说,这可能从错误信息文本中检索数据,不过还好MySQL并没有效仿这一点。
#p#
十、小结
在本文中,我们为读者详细介绍了MySQL数据库在体系结构方面的安全问题以及相应的攻击方法,在后续的文章中我们将介绍相应的防御措施。
【编辑推荐】