COMMAND

    MySQL

SYSTEMS AFFECTED

    All versions of MySQL

PROBLEM

    Following is based on a CORE SDI Security Advisory  CORE-20001023.
    The "MySQL Database Engine" uses an authentication scheme designed
    to prevent the  flow of plaintext  passwords over the  network and
    the  storage   of  them   in  plaintext.    For  that   purpose  a
    challenge-response   mechanism   for   authentication   has   been
    implemented on all versions of MySQL.  Slight variations are to be
    found between version 3.20 and 3.21 and above.

    Regrettably,    this    authentication     mechanism    is     not
    cryptographically strong.  Specifically, each time a user executes
    this mechanism, information allowing  an attacker to recover  this
    user's  password  is  leaked.  Using  an  attack  of  our  design,
    described in the "Technical details" section of this advisory,  an
    eavesdropper  is  able  to  recover  the  user's  password   after
    witnessing only a few executions  of this protocol, and thence  is
    able to authenticate to the database engine impersonating a  valid
    user.

    These vulnerabilities  were found  and researched  by Ariel "Wata"
    Waissbein, Emiliano Kargieman,  Carlos Sarraute, Gerardo  Richarte
    and Agustin "Kato" Azubel of CORE SDI, Buenos Aires, Argentina.

    Technical  Description  -   exploit/concept  code  follows.    The
    challenge-response mechanism devised in MySQL does the  following.
    From mysql-3.22.32/sql/password.c:

        /***********************************************************************
        The main idea is that no passwords are sent between client & server on
        connection and that no passwords are saved in mysql in a decodable
        form.
        
        MySQL provides users with two primitives used for authentication: a
        hash function and a (supposedly) random generator. On connection a random
        string is generated by the server and sent to the client. The client,
        using as input the hash value of the random string he has received and
        the hash value of his password, calculates a new string using the
        random generator primitive.
        This 'check' string is sent to the server, where it is compared with a
        string generated from the stored hash_value of the password and the
        random string.
        
        The password is saved (in user.password) by using the PASSWORD()
        function in mysql.
        
         Example:
           update user set password=PASSWORD("hello") where user="test"
         This saves a hashed number as a string in the password field.
         **********************************************************************/

    To accomplish that purpose  several functions and data  structures
    are implemented:

         mysql-3.22.32/include/mysql_com.h:
          struct rand_struct {
           unsigned long seed1,seed2,max_value;
           double max_value_dbl;
          };

    mysql-3.22.32/sql/password.c:

          void randominit(struct rand_struct *rand_st,ulong seed1, ulong seed2)

    Initializes the PRNG, used by versions 3.21 and up

          static void old_randominit(struct rand_struct *rand_st,ulong seed1)

    Initializes the PRNG, used by versions up to 3.20

         double rnd(struct rand_struct *rand_st)

    Provides a random  floating point (double)  number taken from  the
    PRNG between 0 and rand_st->max_value

         void hash_password(ulong *result, const char *password)

    Calculates a hash of a password string and stores it in 'result'.

         void make_scrambled_password(char *to,const char *password)

    Hashes and stores the password in a readable form in 'to'

         char *scramble(char *to,const char *message,const char *password,
                      my_bool old_ver)

    Genererate a new message based on message and password.  The  same
    thing is done in client and server and the results are checked.

         my_bool check_scramble(const char *scrambled, const char *message,
                              ulong *hash_pass, my_bool old_ver)

    Checks if  the string  generated by  the hashed  password and  the
    message sent matches the string received from the other  endpoint.
    This is the check for the challenge-response mechanism.

    The MySQL engine initializes the PRNG upon startup of the server
    as follows:

         mysql-3.22.32/sql/mysqld.cc:main()
         randominit(&sql_rand,(ulong) start_time,(ulong) start_time/2);

    Where start_time is obtained using  the seconds since 0:00 Jan  1,
    1970  UTC  using  time(3)  when  the  server  starts.   Our  first
    observation is that  the PRNG is  seeded with an  easily guessable
    value.  Though, this observation has no direct implications in the
    vulnerability we present.

    Upon  connection  to  the  server  from  a  client a new thread is
    created to handle it and  a random string is calculate  and stored
    in    per    connection    structure,    this    is    done     in
    mysql-3.22.32/sql/mysqld.cc:create_new_thread():

           ...
           (thread_count-delayed_insert_threads > max_used_connections)
           max_used_connections=thread_count-delayed_insert_threads;
           thd->thread_id=thread_id++;
           for (uint i=0; i < 8 ; i++)         // Generate password teststring
             thd->scramble[i]= (char) (rnd(&sql_rand)*94+33);
           thd->scramble[8]=0;
           thd->rand=sql_rand;
           threads.append(thd);
        
           /* Start a new thread to handle connection */
           ...

    The  challenge/response  exchange  is  performed  and  checked  in
    mysql-3.22.32/sql/sql_parse.cc:check_connections():

           ....
           memcpy(end,thd->scramble,SCRAMBLE_LENGTH+1);
           end+=SCRAMBLE_LENGTH+1;
           ...
           if (net_write_command(net,protocol_version, buff, (uint) (end-buff)) ||
               (pkt_len=my_net_read(net)) == packet_error || pkt_len < 6)
           {
             inc_host_errors(&thd->remote.sin_addr);
             return(ER_HANDSHAKE_ERROR);
           }

    Here the  random string  has been  sent (along  with other  server
    data) and the response has  been read.  The authentication  checks
    are then perfomed

            ...
            char *passwd= strend((char*) net->read_pos+5)+1;
            if (passwd[0] && strlen(passwd) != SCRAMBLE_LENGTH)
              return ER_HANDSHAKE_ERROR;
             thd->master_access=acl_getroot(thd->host, thd->ip, thd->user,
                                        passwd, thd->scramble, &thd->priv_user,
                                        protocol_version == 9 ||
                                        !(thd->client_capabilities &
                                          CLIENT_LONG_PASSWORD));
            thd->password=test(passwd[0]);
            ...

    acl_getroot() in mysql-3.22.32/sql/sql_acl.cc does the  permission
    checks for  the username  and host  the connection  comes from and
    calls the  check_scramble function  described above  to verify the
    valid reponse to the challenge  sent.  If the response  is checked
    valid we say this (challenge and response) test was passed.

    The hash  function provided  by MySQL  outputs eight-bytes strings
    (64 bits), whereas the random number generator outputs  five-bytes
    strings  (40  bits).   Notice  that  as  for  the   authentication
    mechanism described  above, to  impersonate a  user only  the hash
    value  of  this  user's  password  is  needed, e.g. not the actual
    password.

    We  now  describe  why  the  hash  value  of  the  password can be
    efficiently  calculated  using  only  a  few  executions  of   the
    challenge-and-response   mechanism   for   the   same   user.   In
    particular, we introduce a weakness of this authentication scheme,
    and deduce that an  attack more efficient than  brute-force attack
    can be carried out.

    Firstly we describe how  the MySQL random generator  (PRNG) works.
    Then we proceed to analyse this scheme's security.  The  algorithm
    for making  these calculations  will be  briefly described  in the
    following section.

    Let n :=  2^{30}-1 (here n  is the max_value  used in randominit()
    and old_randoninit() respectively).  Fix  a user U.  And  initiate
    a challenge and response.  That is, suppose the server has sent  a
    challenge to the user U.   The hash value of this user's  password
    is 8 bytes  long.  Denote  by P1 the  first (leftmost) 4  bytes of
    this hash value and by P2 the last 4 bytes (rightmost).  Likewise,
    let C1 denote the first 4 bytes of the challenge's hash value  and
    C2 the last 4.  Then, the random generator works as follows:

        -calculate the values seed1 := P1^C1 and seed2 := P2^C2
         (here ^ denotes the bitwise exclusive or (XOR) function)

        -calculate recursively for 1 =< i =< 8
          seed1 = seed1+(3*seed2)         modulo (n)
          seed2 = seed1+seed2+33          modulo (n)
          r[i] = floor((seed1/n)*31)+64

        -calculate form the preceding values
          seed1 = seed1+(3*seed2)         modulo (n)
          seed2 = seed1+seed2+33          modulo (n)
          r[9] = floor((seed1/n)*31)

        -output the checksum value
          S=(r[1]^r[9] || r[2]^r[9] || ... || r[7]^r[9] || r[8]^r[9])

    It  is  this  checksum  that  is  sent,  by U, to the server.  The
    server,  who  has  in  store  the  hash  value  of  U's  password,
    recalculates  the  checksum  by  this  same  process and succintly
    verifies the authenticity of the  value it has received.   However
    it  is  a  small  collection  of  these  checksums that allows any
    attacker  to  obtain  P1  and  P2  (the  hash  value of the user's
    password).   Hence, it  is therefore  possible to  impersonate any
    user with only  the information that  travels on the  wire between
    server and client (user).

    The reason why  the process of  producing the checksum  out of the
    hash values of both the password and the challenge is insecure  is
    that this  process can  be efficently  reversed due  to it's  rich
    arithmetic  properties.   More  specifically,  consider the random
    generator described  above as  a mapping  'f' that  takes as input
    the two values  X and Y  and produces the  checksum value f(X,Y)=S
    (e.g.,  in  our  case  X:=P1^C1   and  Y:=P2^C2).   Then  we   can
    efficiently calculate  all of  the values  X',Y' which  map to the
    same checksum value than X,Y, i.e. if f(X,Y)=S, then we  calculate
    the set of all  the values X',Y' such  that f(X',Y')=S.  This  set
    is of negligible size in comparison to the 2^64 points set of  all
    the possible passwords' hashes in which it is contained.

    Furthermore, given a collection  of challenges and responses  made
    between  the  same  user  and  the  server,  it  is  possible   to
    efficiently calculate the  set of all  (hash values of)  passwords
    passing the given tests.

    We now give a  brief description of the  attack we propose.   This
    description shall enable readers to verify our assertion that  the
    MySQL authentication  scheme leaks  information.   This attack has
    been implemented on Squeak Smalltalk and is now perfectly running.
    A complete  description of  the attack-algorithm  lies beyond  the
    scope of this text and will be the matter of future work.

    The attack  we designed  is mainly  divided into  two stages.   In
    these two stages  we respectively use  one of our  two algorithmic
    tools.

    Procedure  1  is  an  algorithmic  process  which  has  as input a
    checksum  S  and  the  corresponding  hash  value of the challenge
    C1||C2, and outputs a set consisting of all the pairs X,Y  mapping
    through the random  generator to the  checksum S, i.e.  in symbols
    {(X,Y): f(X,Y)=S} (here of course we have 0 <=X,Y< 2^{32}).

    In test  attack Procedure  1 is  used to  cut down  the number  of
    possible hashed  passwords from  the brute-force  value 2^64  to a
    much smaller cardinality of 2^20.  This set is highly  efficiently
    described, e.g. less than 1Kb memory.  For this smaller set, it is
    feasible to eliminate the invalid (hashed) passwords using further
    challenges and responses by our Procedure 2.

    Procedure 2 is an algorithmic process having as input a set SET of
    possible (hashed) passwords, and a new pair (S,C1||C2) of checksum
    and challenge, and  producing as output  the subset of  SET of all
    the passwords passing this new test.

    The way in which Procedure 2  is used in our algorithm should  now
    be  clear.   CORE  first  uses  Procedure  1  to reduce the set of
    passwords to the announced set consisting of 2^{20} points,  using
    as  input  only  two  challenge  and  responses for the same user.
    This  set  contains  all  the  passwords  passing  this two tests.
    Suppose now  that the  attacker has  in his  possession a new pair
    (S,C1||C2) of challenge and response, then he can use Procedure  2
    to produce the smaller set of all the passwords passing the  first
    three  tests  (the  ones  corresponding  to  the  three  pairs  of
    challenge and response he has used). Notice that this process  can
    be  repeated  for  every  new  pair  of challenge and response the
    attacker gets.  With each  application of this process the  set of
    possible passwords becomes smaller.  Furthermore, the  cardinality
    of these sets is not only decresing but eventually becomes 1.   In
    that case the one element remaining is the (hashed) password.

    In the  examples tested,  about 300  possible passwords  were left
    with the use of only 10  pairs of challenge and response.   Notice
    that     in     a      plain     brute-force     attack      about
    2^{64}-300=18,446,744,073,709,551,316  would  remain  as  possible
    passwords.  It took about  100 pairs of challenge and  response to
    cut the 300 set two a set containing two possible passwords (i.e.,
    a fake password and the  password indeed).  Finally it  took about
    300 pairs of challenge and response to get the password.

    We therefore are  able to make  a variety of  attacks depending on
    the amount of  pairs of challenge  and response they  get from the
    user we  want to  impersonate.   The two  extreme cases being very
    few pairs of challenge and response from the same user, and a  lot
    of pairs of  challenge and response.   The second attack,  that of
    many   pairs   of    challenge   and    response   captured,    is
    straight-forward:  Apply the  algorithm described above until  the
    password is found.   The first case, that  of only a few  pairs of
    challenge and  response captured,  is as  well easy  to carry out:
    simply apply  the algorithm  we described  with all  the pairs  of
    challenge and  response captured,  then use  any possible password
    in  the  set  produced  by  the  application  of the algorithm for
    authenticating yourself as  a user (some  of these fake  passwords
    will still pass many tests!).

SOLUTION

    The  vendor  is  aware  of  the  problems  described  and suggests
    encrypting  the  traffic  between  client  and  server  to prevent
    exploitation.  For further details refer to:

        http://www.mysql.com/documentation/mysql/commented/manual.php?section=Security

    Plans to implement a  stronger authentication mechanism are  being
    discussed for future versions of MySQL.