Nmap Development mailing list archives

Re: Vuls of Nmap and OpenSSL cause Ncat Crush


From: Daniel Miller <bonsaiviking () gmail com>
Date: Mon, 30 Mar 2015 07:50:24 -0500

Mitsuaki,

Thank you for this very detailed bug report. If I am not mistaken, this
issue was previously discovered and fixed in r33743 [1] in October 2014.
The fix has not been released, but is in our development version in SVN.
Here's the commit message:

    Fix Ncat crash on concurrent ssl connections

    Reported on debian bugtracker here:
    https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=724580

    We can't remove an fdinfo from client_fdlist and still expect to access
    the fdinfo via a pointer we got from get_fdinfo(&client_fdlist) since
    rm_fd() modifies the data at the address pointed to. So instead of
    removing it from the list and then adding it right back, we just don't
    remove it in the first place.

I'm not sure what we can do about the OpenSSL issue; I do see you have sent
this report to them as well. Can you see any way we can be more careful to
validate the parameters we pass to SSL_accept?

Dan

[1]
https://github.com/nmap/nmap/commit/3b6ea5a9e5cdc2d40f2761b1cdef2fa99d0c1daa

On Sun, Mar 29, 2015 at 8:23 PM, <Mitsuaki_Shiraishi () dell com> wrote:

Hello, Nmap Developers & OpenSSL Developers,

I found two vulnerabilities which cause segmentation fault,
the one is in Nmap and another is in OpenSSL.

============================================
Summary
============================================

* Both of Nmap 6.47 and OpenSSL 1.0.2a has a vulnerability which
  results ncat listener crush
* Nmap 6.47 has a race condition / use-after-free vulnerability
  in ncat listener with ssl support which triggers OpenSSL's bug
* OpenSSL 1.0.2a does not check user input data which causes
  segmentation fault
* Attacker can crush ncat listener remotely by exploiting
  these vulns
* At the worst scenario, remote code execution might be possible.


[Affected Version]
* Nmap 6.47 source compiled version (latest version at 2015/03/30)
* Nmap 6.47 installed Kali, 3.14-kali1-amd64
* Nmap 6.47 installed Debian 7, Debian 3.2.65-1+deb7u2 i686

* OpenSSL 1.0.2a source compiled version (latest version at 2015/03/30)
* OpenSSL 1.0.1e 11 Feb 2013 for Debian 7, Debian 3.2.65-1+deb7u2 i686
* OpenSSL 1.0.1e 11 Feb 2013 for Kali, 3.14-kali1-amd64
* OpenSSL 1.0.1e-fips 11 Feb 2013 for CentOS, 3.10.0-123.20.1.el7.x86_64


[Exploit Example]

# ncat -nvv -l -p 443 -k --ssl
# cd /usr/share/nmap/scripts
# nmap -nvv -r -Pn -sS -p 443 --script=ssl* 127.0.0.1

[Output]
# ncat -nvv -l -p 443 -k --ssl
Ncat: Version 6.47 ( http://nmap.org/ncat )
Ncat: Generating a temporary 1024-bit RSA key. Use --ssl-key and
--ssl-cert to use a permanent one.
Ncat: SHA-1 fingerprint: FAFA 8414 F23D 86FC 0555 8D51 3CFA 2B28 3972 36AA
Ncat: Listening on :::443
Ncat: Listening on 0.0.0.0:443
Ncat: Connection from 127.0.0.1.
Ncat: Connection from 127.0.0.1:46276.
Ncat: Connection from 127.0.0.1.
Ncat: Connection from 127.0.0.1:46277.
Ncat: Connection from 127.0.0.1.
Ncat: Connection from 127.0.0.1:46278.
Ncat: Connection from 127.0.0.1.
Ncat: Connection from 127.0.0.1:46279.
Ncat: Connection from 127.0.0.1.
Ncat: Connection from 127.0.0.1:46280.
Ncat: Connection from 127.0.0.1.
Ncat: Connection from 127.0.0.1:46281.
Ncat: Connection from 127.0.0.1.
Ncat: Connection from 127.0.0.1:46282.
Ncat: Connection from 127.0.0.1.
Ncat: Connection from 127.0.0.1:46283.
Ncat: Failed SSL connection from 127.0.0.1: error:140760FC:SSL
routines:SSL23_GET_CLIENT_HELLO:unknown protocol
Ncat: Failed SSL connection from 127.0.0.1: error:1408A0C1:SSL
routines:SSL3_GET_CLIENT_HELLO:no shared cipher
Ncat: Connection from 127.0.0.1.
Ncat: Connection from 127.0.0.1:46284.
Ncat: Failed SSL connection from 127.0.0.1:
error:00000000:lib(0):func(0):reason(0)
Ncat: Failed SSL connection from 127.0.0.1:
error:00000000:lib(0):func(0):reason(0)
Ncat: Failed SSL connection from 127.0.0.1:
error:00000000:lib(0):func(0):reason(0)
Ncat: Failed SSL connection from 127.0.0.1:
error:00000000:lib(0):func(0):reason(0)
Ncat: Connection from 127.0.0.1.
Ncat: Connection from 127.0.0.1:46285.
Ncat: Connection from 127.0.0.1.
Ncat: Connection from 127.0.0.1:46286.
Segmentation fault


========================================================
A. Nmap race condition / use-after-free vulnerability
========================================================

[A-1] Condition

The vulnerability exposed if events occurs following order below:
* ncat listens with "-k" and "--ssl"
* connection-1 starts ssl-handshake
* connection-2 starts ssl-handshake
* connection-1 complete ssl-handshake
* connection-2 is terminated with ssl_failed
* connection-3 starts ssl-handshake using same file descriptor
  as connection-2


[A-2] Cause

ncat_listen_stream() in ncat/ncat_listen.c
 352  fdi = get_fdinfo(&client_fdlist, i);
 353  ncat_assert(fdi != NULL);
 354  switch (ssl_handshake(fdi)) {
 355  case NCAT_SSL_HANDSHAKE_COMPLETED:
 356      /* Clear from sslpending_fds once ssl is established */
 357      FD_CLR(i, &sslpending_fds);
 358      rm_fd(&client_fdlist, i);
 359      post_handle_connection(*fdi);
 360      break;


rm_fd() in ncat/util.c
 630  fdl->fds[x] = fdl->fds[last - 1];
 631
 632  fdl->nfds--;


post_handle_connection() in ncat/ncat_listen.c
 531  FD_SET(sinfo.fd, &master_readfds);
 532  /* add it to our list of fds for maintaining maxfd */
 533  if (add_fdinfo(&client_fdlist, &sinfo) < 0)
 534  bye("add_fdinfo() failed.");


When NCAT_SSL_HANDSHAKE_COMPLETED, ncat removes current
fdinfo from client_fdlist by calling rm_fd(), then pass
fdi to post_handle_connection().

(image of client_fdlist)
client_fdlist: idx=0, fd 3, ssl=0
client_fdlist: idx=1, fd 4, ssl=0
client_fdlist: idx=2, fd 0, ssl=0
client_fdlist: idx=3, fd 5, ssl=80e33b8
client_fdlist: idx=4, fd 6, ssl=80e3a78 /* COMPLETED */
client_fdlist: idx=5, fd 7, ssl=810edd0
client_fdlist: idx=6, fd 8, ssl=81212e0



But in rm_fd(), removing fdinfo is done by below:
* copy last fdinfo to current position
* decrement array index

Therefore, after returned from rm_fd(), line 358 in
ncat/ncat_listen.c, fdinfo pointed by fdi is the latest
created, not NCAT_SSL_HANDSHAKE_COMPLETED.

(image of client_fdlist)
client_fdlist: idx=0, fd 3, ssl=0
client_fdlist: idx=1, fd 4, ssl=0
client_fdlist: idx=2, fd 0, ssl=0
client_fdlist: idx=3, fd 5, ssl=80e33b8
client_fdlist: idx=4, fd 8, ssl=81212e0 /* Moved from last */
client_fdlist: idx=5, fd 7, ssl=810edd0


At next line(359), post_handle_connection() will add
target fdinfo(again, the latest created fdinfo, not
NCAT_SSL_HANDSHAKE_COMPLETED) to client_fdlist, resulting
duplicates the last created fdinfo.

If the last one is in the way to ssl-handshake,
two entries point to one SSL object.

(image of client_fdlist)
client_fdlist: idx=0, fd 3, ssl=0
client_fdlist: idx=1, fd 4, ssl=0
client_fdlist: idx=2, fd 0, ssl=0
client_fdlist: idx=3, fd 5, ssl=80e33b8
client_fdlist: idx=4, fd 8, ssl=81212e0
client_fdlist: idx=5, fd 7, ssl=810edd0
client_fdlist: idx=6, fd 8, ssl=81212e0 /* Added by
post_handle_connection() */


When latest fdinfo is closed with NCAT_SSL_HANDSHAKE_FAILED,
SSL_free() and rm_fd() are called. the first fdlist related
to latest fdinfo is removed, but duplicated one remains in
client_fdlist.

ncat_listen_stream() in ncat/ncat_listen.c
 367  case NCAT_SSL_HANDSHAKE_FAILED:
 368  default:
 369      SSL_free(fdi->ssl);
 370      Close(fdi->fd);
 371      FD_CLR(i, &sslpending_fds);
 372      FD_CLR(i, &master_readfds);
 373      rm_fd(&client_fdlist, i);

Image of client_fdlist: before rm_fd() is called
client_fdlist: idx=0, fd 3, ssl=0
client_fdlist: idx=1, fd 4, ssl=0
client_fdlist: idx=2, fd 0, ssl=0
client_fdlist: idx=3, fd 5, ssl=80e33b8
client_fdlist: idx=4, fd 8, ssl=81212e0 /* NCAT_SSL_HANDSHAKE_FAILED */
client_fdlist: idx=5, fd 9, ssl=0
client_fdlist: idx=6, fd 8, ssl=81212e0 /* Duplicated */

Image of client_fdlist: after rm_fd() is called
client_fdlist: idx=0, fd 3, ssl=0
client_fdlist: idx=1, fd 4, ssl=0
client_fdlist: idx=2, fd 0, ssl=0
client_fdlist: idx=3, fd 5, ssl=80e33b8
client_fdlist: idx=4, fd 8, ssl=81212e0 /* Deleted, but still remains */
client_fdlist: idx=5, fd 9, ssl=0


Ncat keep accepting new connection. When new connection is
established and same fd with duplicated one is assigned,
ncat grubs duplicated fdlist which contains SSL_free()ed
SSL object.


 348  /* Is this an ssl socket pending a handshake? If so handle it. */
 349  if (o.ssl && FD_ISSET(i, &sslpending_fds)) {
 350      FD_CLR(i, &master_readfds);
 351      FD_CLR(i, &master_writefds);
 352      fdi = get_fdinfo(&client_fdlist, i);
 353      ncat_assert(fdi != NULL);
 354      switch (ssl_handshake(fdi)) {
 355      case NCAT_SSL_HANDSHAKE_COMPLETED:

Image of client_fdlist: after get_fdinfo() is called with fd=8
client_fdlist: idx=0, fd 3, ssl=0
client_fdlist: idx=1, fd 4, ssl=0
client_fdlist: idx=2, fd 0, ssl=0
client_fdlist: idx=3, fd 5, ssl=80e33b8
client_fdlist: idx=4, fd 8, ssl=81212e0 /* obtained */
client_fdlist: idx=5, fd 9, ssl=80fcec0
client_fdlist: idx=6, fd 7, ssl=0
client_fdlist: idx=7, fd 8, ssl=0

Ncat does not call new_ssl() because sinfo->ssl is not NULL.

ssl_handshake() in ncat/ncat_ssl.c
622  /* Initialize the socket too if it isn't.  */
623  if (!sinfo->ssl)
624     sinfo->ssl = new_ssl(sinfo->fd);
625
626  ret = SSL_accept(sinfo->ssl);

Calling SSL_accept() triggers use-after-free condition in
OpenSSL. It results segmentation fault in OpenSSL.

Ncat: Connection from 172.16.223.131.
Ncat: Connection from 172.16.223.131:60634.
Segmentation fault


[A-3] Remediation

* post_handle_connection() should be called before rm_fd() is called.

ncat_listen_stream() in ncat/ncat_listen.c
 355  case NCAT_SSL_HANDSHAKE_COMPLETED:
 356      /* Clear from sslpending_fds once ssl is established */
 357      FD_CLR(i, &sslpending_fds);
 358      post_handle_connection(*fdi); // current fdinfo is copied
 359      rm_fd(&client_fdlist, i); // remove current fdinfo
 360      break;


client_fdlist: idx=0, fd 3, ssl=0
client_fdlist: idx=1, fd 4, ssl=0
client_fdlist: idx=2, fd 0, ssl=0
client_fdlist: idx=3, fd 5, ssl=80e33b8
client_fdlist: idx=4, fd 6, ssl=80e3a78 /* COMPLETED */
client_fdlist: idx=5, fd 7, ssl=810edd0
client_fdlist: idx=6, fd 8, ssl=81212e0


client_fdlist: idx=0, fd 3, ssl=0
client_fdlist: idx=1, fd 4, ssl=0
client_fdlist: idx=2, fd 0, ssl=0
client_fdlist: idx=3, fd 5, ssl=80e33b8
client_fdlist: idx=4, fd 6, ssl=80e3a78 /* COMPLETED */
client_fdlist: idx=5, fd 7, ssl=810edd0
client_fdlist: idx=6, fd 8, ssl=81212e0
client_fdlist: idx=7, fd 6, ssl=80e3a78 /* Added first */


client_fdlist: idx=0, fd 3, ssl=0
client_fdlist: idx=1, fd 4, ssl=0
client_fdlist: idx=2, fd 0, ssl=0
client_fdlist: idx=3, fd 5, ssl=80e33b8
client_fdlist: idx=4, fd 6, ssl=80e3a78 /* Removed */
client_fdlist: idx=5, fd 7, ssl=810edd0
client_fdlist: idx=6, fd 8, ssl=81212e0





========================================================
B. OpenSSL input validation error
========================================================

[B-1] Condition
 * SSL object contains malicious "method" value when SSL_acccept()
   is called


[b-2] Cause
 * In some situation such as ncat bug described above,
   SSL object may have invalid/malicious member.
 * SSL object has a member named "method", which is a pointer
   to struct ssl_method_st.
 * Struct ssl_method_st contains a function pointer "ssl_accept".
 * When SSL_accept() is called, it calls method->ssl_accept().
 * SSL_accept() does not check the value of "method".
 * This causes segmentation fault if the "method" is invalid.
 * (Attacker may put specially crafted "method" which contains
   malicious function pointer. It seems to be possible to execute
   arbitrary code.)


--- Code ---
ssl/ssl_lib.c
 984  int SSL_accept(SSL *s)
 985  {
 986      if (s->handshake_func == 0)
 987          /* Not properly initialized yet */
 988          SSL_set_accept_state(s);
 989
 990      return (s->method->ssl_accept(s));
 991  }


ssl/ssl.h
 437  /* Used to hold functions for SSLv2 or SSLv3/TLSv1 functions */
 438  struct ssl_method_st {
 439      int version;
 440      int (*ssl_new) (SSL *s);
 441      void (*ssl_clear) (SSL *s);
 442      void (*ssl_free) (SSL *s);
 443      int (*ssl_accept) (SSL *s);   /* called by
s->method->ssl_accept(s) in SSL_accept() */
 444      int (*ssl_connect) (SSL *s);


--- PoC ---
ssl = new_ssl(fd);
modify_ssl(ssl);     /* set ssl->method to 0x1234567 */
ret = SSL_accept(ssl);


--- Input invalid SSL object ---
ssl->version = 0xa0b0c0d /* malicious value 1 */
ssl->type = 0xa0b0c0d    /* malicious value 2 */
ssl->method = 0x1234567  /* malicious value 3 causes segfault */
ssl->rbio = 0x8290c50
ssl->wbio = 0x8290c50


--- Debugger ---
Program received signal SIGSEGV, Segmentation fault.
0x080752b8 in SSL_accept ()

(gdb) info registers
eax            0x1234567    19088743 /* the value of ssl->method */
ecx            0xb7f9e36c   -1208360084
edx            0x0  0
ebx            0x82910b8    136908984
esp            0xbffff4b0   0xbffff4b0
ebp            0x1  0x1
esi            0x8291d20    136912160
edi            0x0  0
eip            0x80752b8    0x80752b8 <SSL_accept+56>
eflags         0x210246 [ PF ZF IF RF ID ]
cs             0x73 115
ss             0x7b 123
ds             0x7b 123
es             0x7b 123
fs             0x0  0
gs             0x33 51


(gdb) disas SSL_accept
Dump of assembler code for function SSL_accept:
   0x08075280 <+0>: push   %ebx
   0x08075281 <+1>: sub    $0x18,%esp
   0x08075284 <+4>: mov    0x20(%esp),%ebx
   0x08075288 <+8>: mov    0x20(%ebx),%eax
   0x0807528b <+11>:    test   %eax,%eax
   0x0807528d <+13>:    je     0x80752a0 <SSL_accept+32>
   0x0807528f <+15>:    mov    0x8(%ebx),%eax
   0x08075292 <+18>:    mov    %ebx,0x20(%esp)
   0x08075296 <+22>:    mov    0x10(%eax),%eax
   0x08075299 <+25>:    add    $0x18,%esp
   0x0807529c <+28>:    pop    %ebx
   0x0807529d <+29>:    jmp    *%eax
   0x0807529f <+31>:    nop
   0x080752a0 <+32>:    mov    0x8(%ebx),%eax
   0x080752a3 <+35>:    movl   $0x1,0x24(%ebx)
   0x080752aa <+42>:    movl   $0x0,0x30(%ebx)
   0x080752b1 <+49>:    movl   $0x6000,0x34(%ebx)
=> 0x080752b8 <+56>:    mov    0x10(%eax),%eax
   0x080752bb <+59>:    mov    %eax,0x20(%ebx)
   0x080752be <+62>:    mov    0x80(%ebx),%eax
   0x080752c4 <+68>:    test   %eax,%eax
   0x080752c6 <+70>:    je     0x80752e8 <SSL_accept+104>


[B-3] Remediation

* SSL_accept() should validate the value of ssl->method
  (and values of other members) before use it
* SSL_accept() should return 0 or <0
  https://www.openssl.org/docs/ssl/SSL_accept.html


=========================
Mitsuaki Shiraishi
Mitsuaki_Shiraishi () dell com
-----------------------------------------
Dell SecureWorks
    - CISSP, CISA, GCIH
    - Information Security Specialist
    - Software Design & Development Engineer
=========================


_______________________________________________
Sent through the dev mailing list
https://nmap.org/mailman/listinfo/dev
Archived at http://seclists.org/nmap-dev/

_______________________________________________
Sent through the dev mailing list
https://nmap.org/mailman/listinfo/dev
Archived at http://seclists.org/nmap-dev/

Current thread: