Nmap Development mailing list archives

Vuls of Nmap and OpenSSL cause Ncat Crush


From: <Mitsuaki_Shiraishi () Dell com>
Date: Mon, 30 Mar 2015 01:23:50 +0000

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/


Current thread: