drwetter / testssl.sh

Testing TLS/SSL encryption anywhere on any port
https://testssl.sh
GNU General Public License v2.0
7.92k stars 1.02k forks source link

Support for MS-SQL #558

Open arvinddoraiswamy opened 7 years ago

arvinddoraiswamy commented 7 years ago

The script didn't seem to run out of the box against an MS-SQL server that supports SSL on port 1433. Is this a planned feature? If I can, I'll see what can be done to make it work and submit a pull request.

drwetter commented 7 years ago

did you just try ./testssl.sh <NODE>:<PORT> ?

arvinddoraiswamy commented 7 years ago

That is correct. I also tried with -o starttls=postgres using the newest build, but that didn't (predictably) work. Maybe I am using the wrong switch, idk.

On Sat, Dec 10, 2016 at 2:58 AM, Dirk Wetter notifications@github.com wrote:

did you just try ./testssl.sh : ?

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/drwetter/testssl.sh/issues/558#issuecomment-266200091, or mute the thread https://github.com/notifications/unsubscribe-auth/AA2D4rqnB1B8Ex4rlM9VnnCIzvhCgiqpks5rGoXhgaJpZM4LIWpa .

drwetter commented 7 years ago

Am 11. Dezember 2016 04:25:19 MEZ, schrieb arvinddoraiswamy notifications@github.com:

That is correct. I also tried with -o starttls=postgres using the newest build, but that didn't (predictably) work. Maybe I am using the wrong switch, idk.

I couldn't find any hint that mssql supports STARTTLS. And even if it would, the upgrade from plaintext to TLS for sure would be different.

If you have the chance I recommend to look into the network traffic.

Dirk

-- Sent from my mobile. Excuse my brevity&typos+the phone's autocorrection

arvinddoraiswamy commented 7 years ago

Okay i will do that later if i can, thanks.

On Sun, Dec 11, 2016 at 12:53 AM, Dirk Wetter notifications@github.com wrote:

Am 11. Dezember 2016 04:25:19 MEZ, schrieb arvinddoraiswamy < notifications@github.com>:

That is correct. I also tried with -o starttls=postgres using the newest build, but that didn't (predictably) work. Maybe I am using the wrong switch, idk.

I couldn't find any hint that mssql supports STARTTLS. And even if it would, the upgrade from plaintext to TLS for sure would be different.

If you have the chance I recommend to look into the network traffic.

Dirk

-- Sent from my mobile. Excuse my brevity&typos+the phone's autocorrection

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/drwetter/testssl.sh/issues/558#issuecomment-266270684, or mute the thread https://github.com/notifications/unsubscribe-auth/AA2D4sIUUf7ZNTFST5a9BxCOFVILzK-uks5rG7oAgaJpZM4LIWpa .

drwetter commented 7 years ago

timeout.

Will be reopened upon new information

nrathaus commented 5 years ago

Hi,

I am able to reconfirm this bug - I am not sure whether it is or not a bug as the SSL certificate (and SSL interface) is encapsulated inside what looks like a TDS packet - specifically inside "Pre-Login Message - TLS exchange" - as seen in packet #31 inside the sample

Nmap has no issue finding the certificate details (as well as the SSL/TSL support):

# nmap -sV --script ssl-cert -p 1433 192.168.15.110

Starting Nmap 7.40 ( https://nmap.org ) at 2019-05-05 16:41 IDT
Stats: 0:00:11 elapsed; 0 hosts completed (1 up), 1 undergoing Service Scan
Service scan Timing: About 100.00% done; ETC: 16:41 (0:00:00 remaining)
Nmap scan report for 192.168.15.110
Host is up (-0.15s latency).
PORT     STATE SERVICE  VERSION
1433/tcp open  ms-sql-s Microsoft SQL Server
| ssl-cert: Subject: commonName=DESKTOP-UGNHR6M
| Issuer: commonName=DESKTOP-UGNHR6M
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha1WithRSAEncryption
| Not valid before: 2019-05-05T11:48:05
| Not valid after:  2029-05-02T11:48:05
| MD5:   6d4f 72cd 9865 ee2b b027 e3a2 5765 5e1a
|_SHA-1: ad7c bc52 c909 1b71 de68 f4ef 9571 f249 6a65 8422
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port1433-TCP:V=7.40%I=7%D=5/5%Time=5CCEE825%P=x86_64-pc-linux-gnu%r(ms-
SF:sql-s,25,"\x04\x01\0%\0\0\x01\0\0\0\x15\0\x06\x01\0\x1b\0\x01\x02\0\x1c
SF:\0\x01\x03\0\x1d\0\0\xff\x0e\0\x03\xe8\0\0\0\0");
MAC Address: 0C:9D:92:14:4E:A8 (Unknown)
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 11.92 seconds

I am also attaching the packet dump you would see from running the nmap line

Let me know if you need additional information

mssql-via-nmap.zip

drwetter commented 5 years ago

This is no SSL/TLS handshake. A wireshark I have here interprets that as TDS. Even if I change the interpretation to "SSL" wireshark doesn't see any SSL/TLS traffic.

#30 contains a sequence 1603, then 0000. So even if it looks like TLS, it is not. Please also don't get confused by nmap's interpretation. Not everything which has an RSA certificate with attributes similar to SSL/TLS has to be SSL/TLS.

nrathaus commented 5 years ago

I agree it's not direct SSL/TLS - rather encapsulated - if you offset the processed data to the SSL part inside the TDS packet it can interpret it - if I am not mistaken to interpert the data

In any case - if you don't plan to support this - I would understand

On Sun, 5 May 2019 at 17:07, Dirk Wetter notifications@github.com wrote:

This is no SSL/TLS handshake. A wireshark I have here interprets that as TDS. Even if I change the interpretation to "SSL" wireshark doesn't see any SSL/TLS traffic.

30 https://github.com/drwetter/testssl.sh/pull/30 contains a sequence

1603, then 0000. So even if it looks like TLS, it is not. Please also don't get confused by nmap's interpretation. Not everything which has an RSA certificate with attributes similar to SSL/TLS has to be SSL/TLS.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/drwetter/testssl.sh/issues/558#issuecomment-489429828, or mute the thread https://github.com/notifications/unsubscribe-auth/AAPQE3SUIBQOJMSJIGU4VXLPT3S27ANCNFSM4CZBNJNA .

-- Thanks, Noam Rathaus

mzpqnxow commented 2 years ago

I know this is a long-closed issue but it caught my interest recently. As stated in the discussion above, the SSL/TLS bits happen encapsulated within TDS. To do this with testssl.sh, an additional starttls method would need to be added to the OpenSSL fork testssl.sh uses

This is no SSL/TLS handshake. A wireshark I have here interprets that as TDS. Even if I change the interpretation to "SSL" wireshark doesn't see any SSL/TLS traffic.

30 contains a sequence 1603, then 0000. So even if it looks like TLS, it is not. Please also don't get confused by nmap's interpretation. Not everything which has an RSA certificate with attributes similar to SSL/TLS has to be SSL/TLS.

I think you may have glanced too quickly at that message and misread what looked like an extra null byte but was actually one half of the 16bit message length- I did the same thing at first, actually- but then I ripped each of the messages out of the pcap using dd and tested them all (Client Hello, Server Hello/Server Cert/Server Hello Done) against an openssl s_client and an openssl s_server, with Wireshark listening as well. Both directions worked fine, OpenSSL liked it and Wireshark decoded it properly

To see what I mean, here is frame #30 that you referenced (sans the layer 2,3 and 4 headers)

$ dd if=frame30.raw bs=1 skip=62  > client-hello.raw
$ xxd client-hello.raw
00000000: 1603 0000 3501 0000 3103 035c cee8 2551        -- handshake=0x16, TLS1.0=0x0300 length=0x0035
00000010: 5555 4a49 5546 594b 5445 5342 5a54 4e4f        --- note the random field is uppercase alphabetical, not great heh...
00000020: 474c 444e 5a44 4a47 5455 4e00 000a 002f
00000030: 000a 0013 0039 0004 0100
... start up openssl s_server ...
$ cat client-hello.raw | nc 127.0.0.1 9999

The s_server accepts it just fine:

$ openssl s_server -accept :9999 -tls1 -debug -msg
Using default temp DH parameters
ACCEPT
read from 0x55ec9dd8 [0x55ed4fe3] (5 bytes => 5 (0x5))
0000 - 16 03 00 00 35                                    ....5
<<< ??? [length 0005]
    16 03 00 00 35
read from 0x55ec9dd8 [0x55ed4fe8] (53 bytes => 53 (0x35))
0000 - 01 00 00 31 03 03 5c ce-e8 25 51 55 55 4a 49 55   ...1..\..%QUUJIU
0010 - 46 59 4b 54 45 53 42 5a-54 4e 4f 47 4c 44 4e 5a   FYKTESBZTNOGLDNZ
0020 - 44 4a 47 54 55 4e 00 00-0a 00 2f 00 0a 00 13 00   DJGTUN..../.....
0030 - 39 00 04 01 00                                    9....
<<< TLS 1.2Handshake [length 0035], ClientHello
    01 00 00 31 03 03 5c ce e8 25 51 55 55 4a 49 55
    46 59 4b 54 45 53 42 5a 54 4e 4f 47 4c 44 4e 5a
    44 4a 47 54 55 4e 00 00 0a 00 2f 00 0a 00 13 00
    39 00 04 01 00
>>> ??? [length 0005]
    16 03 01 00 4a
>>> TLS 1.0Handshake [length 004a], ServerHello
    02 00 00 46 03 01 7c 6d 66 23 4b 46 a1 7a 85 84
    b2 62 3a b7 3b 61 69 3d 1a 6d c8 62 d4 ca 34 aa
    ae 30 68 ae d9 bb 20 7f 6f 58 69 96 03 f2 57 3c
    d0 d8 a7 09 8a 42 d2 90 c8 f7 72 22 ac d7 26 e0
    a7 1f 82 e3 fd 95 7c 00 2f 00
>>> ??? [length 0005]
    16 03 01 03 91
>>> TLS 1.0Handshake [length 0391], Certificate

Still, your point is valid that it's not a simple STARTTLS <vanilla SSL/TLS stream> flow like many (all?) of the other STARTTLS-based protocols, but the good news is that there's only minimal encapsulation around the handshake messages and inside they're untouched. They seem to be mostly opaque to TDS. There's no marshalling, fragmentation, padding or anything else. Plus, because it all takes place in the TDS7 pre-login message type, there aren't many state changes to worry about, it's just a matter of stripping the TDS message header and options out of the stream

From this, I'm concluding it wouldn't be extremely difficult to add a -starttls tds7 or -starttls mssql feature to the fork of OpenSSL, though it might require a little hackery to pull the TDS data off the socket before OpenSSL consumes them. I haven't looked at how the other STARTTLS implementations work, but I'm sure this could be done with MSG_PEEK, explicitly pulling off the TDS bytes before handing the socket over to OpenSSL. Or it could be done via a pipe/shim socket. This would be enough to get the protocol, cipher-suites and certificate info, where testssl.sh can provide a ton of value

I may take a crack at this, though it's discouraging that nobody else seems too interested in it :/

EDIT: messing around with this for 3-4 hours, I was able to get s_client -starttls tds -tls1_1 to agree on a cipher-suite and get the server certificate with ~50 lines of added code to s_client.c. It requires some modifications of one of the callbacks though, as far as I can tell. It's my first use of the OpenSSL API so there's probably a simpler and less invasive way to do it, but it at least proves it out as not too difficult, especially for someone else that is experienced with OpenSSL. One caveat, I only designed it for and tested it on Azure PaaS MSSQL- it probably requires more consideration to be compatible with older MSSQL server versions

drwetter commented 2 years ago

It sounds of course interesting! I can't tell either it's always just the first 30 bytes, as you indicated in the last sentence. Is there some documentation from MS, other parties of the protocol or a e.g. dissector documentation of wireshark or the lua script from nmap? There are some references on Wikipedia (https://en.wikipedia.org/wiki/Tabular_Data_Stream) and while following a link from MS I found this: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/60f56408-0188-4cd5-8b90-25c6f2423868

Though it's interesting from a research perspective we should be careful. If we built something which only works for MSSQL version X or a special Azure version. IMO we need more data here. Also I am not sure how tear down of the TLS connection is the same?

The first impression while reading to add a -starttls tds7 or -starttls mssql feature to the fork of OpenSSL my first reaction was "oh, no. I don't want to touch it for that". But then I realized that we went through that before with mysql and postgres before. You can have a look @ https://github.com/drwetter/openssl-1.0.2.bad/blob/1.0.2-chacha/apps/s_client.c (kudos to @sdann).

Bottom line: we need to find out whether it works for all MSSQL versions like you indicated. Either by research in some docu or/and If you have a working s_client.c we could just do some saber rattling and ask people to test.

Please keep in mind we also need a pure socket implementation in testssl.sh. If it's always the first 3x bytes which should be stripped that should be easy. (maybe then we should discuss whether the term STARTTLS is appropriate but I have no idea about TDS yet. PS: The first two TDS7 login messages wireshark shows me from the zipped pcap above aren't containing a embedded TLS stream, or do I overlook something?).

mzpqnxow commented 2 years ago

It sounds of course interesting! I can't tell either it's always just the first 30 bytes, as you indicated in the last sentence. Is there some documentation from MS, other parties of the protocol or a e.g. dissector documentation of wireshark or the lua script from nmap? There are some references on Wikipedia (https://en.wikipedia.org/wiki/Tabular_Data_Stream) and while following a link from MS I found this: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/60f56408-0188-4cd5-8b90-25c6f2423868

The best documentation I could find is from the FreeTDS project, though it wasn't quite perfect and even they use some opaque blobs in their code (hah!)

One is here, the other is in HTML format here

Though it's interesting from a research perspective we should be careful. If we built something which only works for MSSQL version X or a special Azure version. IMO we need more data here. Also I am not sure how tear down of the TLS connection is the same?

Agreed, I'm not so sure it will be trivial to do a full implementation of this

The first impression while reading to add a -starttls tds7 or -starttls mssql feature to the fork of OpenSSL my first reaction was "oh, no. I don't want to touch it for that". But then I realized that we went through that before with mysql and postgres before. You can have a look @ https://github.com/drwetter/openssl-1.0.2.bad/blob/1.0.2-chacha/apps/s_client.c (kudos to @sdann).

Yeah, I was surprised at how many were in there- including the Telnet one. I think I've seen Telnet with the SSL extensions precisely once in real life, I think in a Kerberized telnetd :)

Bottom line: we need to find out whether it works for all MSSQL versions like you indicated. Either by research in some docu or/and If you have a working s_client.c we could just do some saber rattling and ask people to test.

I wouldn't call what I have working exactly, though if I spend some time reading how to properly use the OpenSSL callbacks I could get it to such a state. Right now it uses a bunch of hacky globals and static variables for keeping the (admittedly simple) state between the callback and main() of s_client.c. It's laughably bad- in one spot I essentially told the callback "eat bytes until you get to the next handshake packet" - it worked up to the certificate exchange and cipher-suite negotiation but it's not too readable and there are invasive changes in places there need not be if done properly

Please keep in mind we also need a pure socket implementation in testssl.sh. If it's always the first 3x bytes which should be stripped that should be easy. (maybe then we should discuss whether the term STARTTLS is appropriate but I have no idea about TDS yet.

Yikes- yeah, that will probably be a bit difficult. Worst case scenario there could be some shim added (afterall, most people depend on the patched OpenSSL binary that's distributed with it, so why not add another one?) but I'm sure you'd like to avoid distributing more executables- which is understandable

PS: The first two TDS7 login messages wireshark shows me from the zipped pcap above aren't containing a embedded TLS stream, or do I overlook something?).

All you're missing is the first 8 bytes- I probably didn't explain too clearly what I did, but to make sure I wasn't complete crazy, before I started looking deeper, I ripped each packet out of wireshark then dd'd out the first 8 bytes of the TCP data. Then I did a cat raw | nc localhost 8443, just to see what Wireshark made of it. For the server side I just had a .py that did roughly the same. It would be nice if you could tell Wireshark to realign its protocol decoder/move it to an arbitrary offset in the frame, but I'm not sure it can do that

EDIT: Also, there's this lovely bit- it's not only the 8 byte header.. please don't laugh at this. Check out the getenv() ... I didn't know how to pre-calculate the length of the packet OpenSSL was going to generate, so I had to brute force it while testing (for i in $(seq 100 400); do echo Try $i ; L=$i ./openssl s_client -connect host:1433 -starttls tds; done)


        char *tds_message;
        tds_message = malloc(1024);
        memset(tds_message, 0x0, 1024);
        struct tds_header * thdr = (struct tds_header *)(tds_message);
        char *tds_data_ptr = (char *)(tds_message + sizeof(struct tds_header));
        int bytes;

        unsigned char tds7_prelogin_msg2[] = {
                0x12, 0x01, 0x00, 0x29, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x15, 0x00,
                0x06, 0x01, 0x00, 0x1b, 0x00, 0x01, 0x02, 0x00, 0x1c, 0x00, 0x01, 0x03,
                0x00, 0x1d, 0x00, 0x04, 0xff, 0x09, 0x00, 0x05, 0x77, 0x00, 0x00, 0x01,
                0x00, 0x00, 0x00, 0x00, 0x00
        };
        unsigned int tds7_prelogin_msg2_len = 41;

        BIO_write(sbio, tds7_prelogin_msg2, tds7_prelogin_msg2_len);
        (void)BIO_flush(sbio);
        ...

        // Read the response message header
        bytes = BIO_read(sbio, sbuf, BUFSIZ);
        // 37 is a magic number
        if (sbuf[0] != 0x4) {
            fprintf(stderr, "FAIL, %d / %x\n", bytes, sbuf[0]);
            goto shut;
        }
        if (bytes != 37) {
            fprintf(stderr, "WARNING: Expected a 37 byte response, but this isn't really a hard requirement...\n");
        }

        unsigned char pre_handshake_header[] = {
                0x12, 0x01, 0x00, 0x42, 0x00, 0x00, 0x02, 0x00,
                // Client Hello follows
        };
        unsigned int pre_handshake_header_len = 8;

        char buf[8];
        buf[0] = 0x12;
        buf[1] = 0x1;
        buf[4] = 0x0;
        buf[5] = 0x0;
        buf[6] = 0x2;
        buf[7] = 0x0;
        ...
        int client_hello_guessed_len = atoi(getenv("L"));
        BIO_printf(bio_c_out, "Guestimating %d for SSL Client Hello (TDS payload)\n", client_hello_guessed_len);
        client_hello_guessed_len &= 0x0000ffff;
        *(uint16_t *)(buf + 2) = (uint16_t)(htons(client_hello_guessed_len));
        BIO_printf(bio_c_out, "Writing TDS header\n");
        BIO_write(sbio, buf, 8);
        (void)BIO_flush(sbio);
        BIO_printf(bio_c_out, "Receiving TDS header and proceeding with SSL negotiation\n");

In the callback, it's even worse:

    ...
    tds_state += 1;
    int bytes;
    char sbuf[16];
    int i=0;
    if (tds_state > 31 && tds_state < 40) {
        while (tds_state < 40) {
            // fprintf("Trying to read %p\n", bio);
            BIO_printf(out, "Eating %d TDS bytes (state: %d bytes eaten, %d remain) ...\n", 1, tds_state, 39 - tds_state);
            bytes = BIO_read(bio, &sbuf[i], 1);
            if (bytes != 1) abort();
            fprintf(stderr, "Ate %d (total of %d)\n", bytes, tds_state);
            tds_state += bytes;
        }

        if (tds_state == 41) {
            BIO_printf(out, "TDS header:\n");
            BIO_dump(out, sbuf, 8);
            BIO_printf(out, "\n");
        }
    ...