I took a look at the authentication mechanisms of the native network protocols of some of the more prominent dbms vendors. One of my targets was Microsofts SQL Server 2008 R2. MSSQL provides two methods for authentication: Integrated and Native Authentication. Integrated uses Windows OS user credentials to log into the database. Native Authentication uses login credentials stored inside the SQL Server.
MSSQL uses the Tabular DataStream protocol on the application layer. The specification is open for everyone to see at mssqlspec. Native Authentication is done inside the TDS protocol. So let's take a look at that.
TDS uses no challenge-response-protocol to transmit the login credentials. There is a message called LOGIN7, which contains username in cleartext and an obfuscated version of the password. The obfuscation algorithm is known and described in the specification. So it's actually cleartext. So I thought: "Nice! Easy sniffing.". Let's have a look at typical login traffic in wireshark:
As you can see there is no LOGIN7 packet, which should be decoded by wireshark. But if we have a closer look at the transmitted packets, we discover SSLish patterns (like the "SSL Self Signed Fallback" certificate). Digging deeper into the specification, I found out, that Microsoft does something very strange here. They use SSL inside their application layer protocol. The whole SSL handshake is wrapped inside those PRELOGIN packets. Wireshark let's us decode the login traffic as SSL and we can see that packet number 10 can be decoded now as "SSL Application Data" record.
So we can't really look into this SSL mess. Which sucks. So my first guess was to do the usual SSL MITM, serving a fake certificates. It turns out, this is pretty annoying to code, because of the handshake inside PRELOGIN thing. Maybe I'll hack something together another time. Fortunately it turns out, there is an easier way :)
The first packet, a PRELOGIN packet, is sent by the client to the server. The server answers with another packet of the same kind. It contains several fields to ensure compatibility, such as version, instance name and encryption :)
The possible values for the ENCRYPTION field are the following:
|ENCRYPT_OFF||0x00||Encryption available but off.|
|ENCRYPT_ON||0x01||Encryption is available and on.|
|ENCRYPT_NOT_SUP||0x02||Encryption is not available.|
|ENCRYPT_REQ||0x03||Encryption is required.|
This values can be configured on the client and server. Default for both is ENCRYPT_OFF, which means that only the LOGIN7 packet is encrypted. One can force Encryption of the whole connection on the client and server.
It is specified how the client/server react to the value of the encryption field the other one sends:
|ENCRYPT_ON||ENCRYPT_ON||ENCRYPT_ON||ENCRYPT_NOT_SUP (connection terminated)|
|ENCRYPT_NOT_SUP||ENCRYPT_NOT_SUP||ENCRYPT_REQ (connection terminated)||ENCRYPT_NOT_SUP|
So a Client cannot differntiate between a server, which does not support encryption at all, and a server which accepts a connection from a client not supporting encryption.
So let's assume we are able to do a MITM attack and are able to modify the traffic passing through us. So we can just change the value of the encryption field to ENCRYPT_NOT_SUP and we get a plaintext LOGIN7 packet. Yay :) I implemented this as some ugly scapy code, but also as a fancy metasploit module. Using this attack we can see something like this in wireshark:
Disclosure & Mitigation
So I told Microsoft about this flaw in september and they told me the following:
Please note that SQL Server does not offer an option to enforce encryption of only the login packet (a.k.a. username & password), and at this point we have no plans to introduce such option.
Ok so no plans to change something here. So I guess the best option is to use the Force Encryption option of your server and clients to encrypt the whole connection or switch to integrated authentication, which is Microsofts recomendation anyway.