In the previous blog post, an introduction to IPv6 Extension headers was given. In this blog post, I will demonstrate how to use Chiron to construct arbitrary IPv6 Extension header chains.
Using Chiron you can define a list of the IPv6 Extension Headers that comprise the IPv6 header chain. To do so, you can use the following Chiron switches:
-lfE <comma_separated_list_of_headers_to_be_fragmented> Define an arbitrary list of Extension Headers which will be included in the fragmentable part.
-luE <comma_separated_list_of_headers_that_remain_unfragmented> Define an arbitrary list of Extension Headers which will be included in the unfragmentable part.
Note: In this section we will create an unfragmented IPv6 header chain only. Later, we shall also demonstrate how fragmentation is performed in IPv6 and how to create fragmented IPv6 header chains using Chiron.
The supported by Chiron IPv6 Extension Headers are the following:
IPv6 Extension Header
Fragment Extension Header
Destination Options Header
Any other value
IPv6 Fake (non-existing) Header
To add an IPv6 Extension header, you just need to use the corresponding header value, as shown in the examples below.
For instance, if you want to add a Destination Options header to perform ping scan (-sn), you can use the following Chiron command:
./chiron_scanner.py eth0 -d 2001:db8:1:1:e633:1ba7:95d0:c943 -sn -luE 60
In the above command:
eth0 is the network interface to be used by Chiron
-d <ipv6 address> is the IPv6 address of the target.
-sn is the network scan to be performed (ping scan)
Similarly, to add a Hop-by-Hop Header and a Destination Options header during a ping scan (-sn), you can use the following Chiron command:
./chiron_scanner.py eth0 -d 2001:db8:1:1:e633:1ba7:95d0:c943 -sn -luE 0,60
To add a Hop-by-Hop and three Destination Options header in a row during a ping scan (-sn), you can just “multiply” the header value with the number of times that you want to include this header in a row in the chain, as follows (please use a capital ‘X’):
./chiron_scanner.py eth0 -d 2001:db8:1:1:e633:1ba7:95d0:c943 -sn -luE 0,3X60
As simple as that.
The packet generated by the above command is depicted in the wireshark screenshot below:
As we can see, we have sent a simple ping request by adding in the IPv6 chain a Hop-by-Hop Extension header and three (3) Destination Option headers.
This is the first of a series of blog posts aiming at describing how to use Chiron to abuse IPv6, and especially IPv6 Extension Headers. However, in order to understand the possibilities that exist with regard to the abuse of IPv6 Extension Headers, we first need to understand the related standardisation foundation.
My objective is that when this series of the blog posts come to an end, the reader shall be able not only to reproduce all the known related attacks, but also to compile and implement his own attack methods. And this is exactly the power of Chiron: it does not just implement known attacks (there are other tools that do it really well), but it provides you the ability and flexibility to implement any kind of related attack, new or not, without having to write a single line of code.
IPv6, initially introduced with [RFC 1883] and [RFC 2460], and lately updated with [RFC 8200], brings several important changes to the IP protocol. Among them, as stated in these RFCs, is the “Improved Support for Extensions and Options”. Specifically, it is described that “changes in the way IP header options are encoded allows for more efficient forwarding, less stringent limits on the length of options, and greater flexibility for introducing new options in the future”.
Whilst “greater flexibility” and “less stringent forwarding” are usually welcome from operational and interoperability perspective, typically they also introduce security implications. As several publications at security conferences or even RFCs has shown (see for example [RFC 7112]), IPv6 Extension Headers unfortunately is not an exception. These security implications become even more important when combined with fragmentation (but I will talk about them in another article).
In this article I will first refresh our memory by summarising the IPv6 Extension Headers concept. Despite my objective is not to discuss extensively and analyse the security implications themselves that arise from the support of IPv6 Extension Headers, I will summarise them briefly and I will point to the right publications.
pfSense is a clone of m0n0wall and, to the best of my knowledge, the eldest open source IPv6 firewall which is still maintained by its developers. Therefore, it should be expected that its maturity level should be good enough for normal usage.
The latest pfSense version currently available is 2.3.3, based on FreeBSD 10.3-RELEASE-p16.
pfSense provides the same capabilities with OPNsense regarding the IPv6 configuration of its interfaces, the deployment of DHCPv6 server, the sending of Router Advertisements and their configuration, etc. So, the only difference from an IPv6 configuration perspective between pfSense and OPNsense is the capability of filtering IPv6 Extension headers, which, nevertheless, does not seem to really work.
Despite the lack of a filtering capability for IPv6 Extension headers, I tested pfSense as an IPv6 firewall. I will spare you most of the details, I will focus on a weird situation that I encountered.
First, I easily noticed (e.g. by introducing some deliberate delay between fragments of the same datagram) that pfSense performs deep packet inspection (i.e. it does not forward any fragment, until it receives them all to reassembly and examine the initial pre-fragmentation IPv6 datagram).
Then, during some legitimate fragmentation testing case, I noticed that I was receiving an ICMPv6 Parameter Problem / Erroneous header field encountered message (see first figure), but I did not understand why. As we can see from this figure, the packets that were sent where two fragments of a TCP (SYN) header, the first of which was 8 bytes long whilst the second (last) was 12 bytes long. This split is a completely legitimate one according to the procedure described in [RFC 2460].
The two fragments and the response received back are depicted at the first Wireshark output.
To identify the reason, I had to capture and examine the traffic as received at the end host (the final “target”). This is depicted in the next Wireshark screenshot, which in my opinion explains a lot: It seems that pfSense after examining the IPv6 datagram, it refragments it in two fragments, so as to send them as they were received. However, due to a bug, presumably, the first fragment is 12 bytes long and the second, inevitably, 8 bytes long. However, as [RFC 2460] clearly explains, all but the last fragments should be multiple of octets of bytes long. Given that the offset of the fragments is also denoted in octets of bytes (e.g. offset at a Fragment Extension header equal to 1 denotes an actual offset of 8 bytes long, offset equal to 2 denotes an actual offset of 16 bytes, and so on), the aforementioned split creates inevitably fragmentation overlapping.
In such a case, the final recipient, if implements [RFC 5722], it will simply drop all the fragments of such a datagram and the consequences will “only” be operational ones (i.e. legitimate traffic will be dropped, without a reason). However, in case the final target accepts fragmentation overlapping, and especially if the newest fragments override the previous received, this could have some security implications. Specifically, an attacker could carefully craft a fragmented datagram so as to avoid firewall detection, but to be acceptable from the final target. This would lead to firewall evasion (in an a new post, after this issue is patched, I may explain how this could be achieved).
In the meantime, the mitigation would be: a) to patch all end systems so as to implement [RFC 5722] (and thankfully this is typically the case for the majority of the modern Operating Systems), and b) to do drop the fragmented packets at the perimeters. Of course, in case that you use pfSense, this could be achieved using the command line only since such an option is not available in the Web interface (in a new post I may also explain how do it).
A final thought: Given that pfSense is a web interface for some FreeBSD capabilities, it could be the case that the aforementioned bug is actually a FreeBSD bug of version of version 10.3 p16. Therefore, I have reported the issue to both FreeBSD and pfSense sec teams.
As the Cisco Labs measurements show, IPv6 is a protocol that cannot be ignored any more. In some countries, like Belgium, Greece, Germany, the US, etc. the percentage of the users employing IPv6 is about 30% or even to 50%, and, based on the estimations, the increase of IPv6 traffic will continue to grow exponentially. So, it’s time to ensure that our firewall supports IPv6 as well.
While there are several open-source based solutions regarding firewalls, Linux-based or FreeBSD-based ones, this is not also the case when we want IPv6 support as well. Since m0n0wall project has officially ended, the only two options actually left for open-source users seeking for an iPv6 firewall are OPNsense and pfSense (if someone has an additional suggestion, please let me know).
Whilst pfSense supports IPv6 for quite a long time, as a firewall from a security perspective has a significant disadvantage: As of version 2.3.3 Community Edition it does not allow the filtering of IPv6 datagrams based on the used IPv6 Extension Headers. Therefore, if its administrator wants to filter e.g. IPv6 traffic carrying a Hop-by-Hop header, a Destination Options header, etc. (see [RFC 2460] for more details on IPv6 Extension headers), he simply cannot do it. And I do consider the capability of filtering IPv6 Extension headers really important for the reasons demonstrated here and here. In my opinion, this capability should be configurable.Therefore, I decided to give OPNsense a try since it seems to be the only open-source solutions that currently offers IPv6 Extension headers filtering capabilities.
At the time of this writing, the latest available version of OPNsense is 17.1.4, based on FreeBSD 11.0.
As far as IPv6 is concerned, OPNsense provides all the options typically required to configure the interfaces of a device for IPv6 usage (e.g. static, using DHCPv6, SLAAC, etc.).
The user has also the capability to configure the IPv6 addresses of the LAN hosts by various means, e.g. by sending Router Advertisements (RAs), by operating a DHCPv6 server, etc. Furthermore, the configuration options provided per case seem to be more than enough. For instance, regarding RAs, it can be configured whether the Managed bit should be set or not, the router priority, the minimum and maximum intervals between RAs, etc. (see figure at the right).
However, given that I was mainly interested in the IPv6 filtering capabilities that OPNSense provides, let’s jump directly to them.
OPNsense at the “Protocol” field provides the options to select for filtering various IPv6 headers, including an IPv6 (encapsulated) header, ICMPv6, as well as some Extension headers like IPv6 Routing header, Fragment Extension header, IPv6 Options header (without clarifying here if it for Destination Options header, Hob-by-hop header, or for both), etc. (see figure at he right).
Of course, all the above configuration capabilities would be of a low significance if they were not tested. For this reason, some basic testing was performed by using my tool, Chiron. As a target IPv6 address, the LAN IPv6 address of the firewall itself was used (so as to check at the same time, when possible, both the filtering capabilities as well as the OPNsense/FreeBSD compliance regarding IPv6 Extension headers). To this end, a rule was added to allow http traffic over IPv6 to the LAN IPv6 address.
After the above configuration, it was found out that by default the following traffic is allowed:
Unfragmented HTTP over IPv6 (as obviously was expected).
Hop-by-hop header, Destination Options header, or their combination (only in the correct order).
Up to 14(!) Destination Options headers in one unfragmented datagram (certainly this is not a compliant behaviour from FreeBSD).
Fragmented HTTP over IPv6.
Fragmented packets of the aforementioned use cases (e.g. multiple Extension headers in fragmented datagrams).
On the contrary, by default no responses are received at the following use-cases, probably not because of firewall filtering but due to compliance of the host to the corresponding RFCs:
Routing header (all types, e.g. Type 0, Type 1, etc. - Type 0 has been deprecated with RFC 5095, the others potentially due to lack of support).
More than one Hop-by-hop headers in unfragmented packets (which is clearly forbidden in [RFC 2460]).
As defined in RFC 2675 [Borman, Deering, Hinden, 1999], “a "jumbogram" is an IPv6 packet containing a payload longer than 65,535 octets”. An IPv6 datagram, due to the length of the IPv6 main header (16 bits), can support up to 65535 octets of IPv6 payload [Deering, Hinden, 1998]; this includes any IPv6 extension headers that may follow the IPv6 main header, but not the main header itself. For applications that need datagrams bigger than this (by the way, are you aware of any of this kind?), IPv6 jumbograms come into place. However, as also explained in RFC 2675, “jumbograms are relevant only to IPv6 nodes that may be attached to links with a link MTU greater than 65,575 octets” (65,535 octets of an IPv6 payload plus 40 octets for the IPv6 main header itself).
IPv6 jumbograms are defined as an IPv6 hop-by-hop option, called the “Jumbo Payload” option, that carries a 32-bit length field in order to allow transmission of IPv6 packets with payloads between 65,536 and 4,294,967,295 octets in length [Borman, Deering, Hinden, 1999].
The Payload Length field in the IPv6 header must be set to zero, while the Next Header value is also set to 0 (implying that a Hop-by-Hop header follows). So, an IPv6 Jumbogram would look like:
Now, the Hop-by-Hop header itself will look like:
where “Next header” is an 8-bit field whose value indicates the header that follows (e.g. if the next header is ICMPv6, it should be 58).
0 is the value of the Length field of the Hop-by-Hop header (the length of the Hop-by-Hop Options header in 8-octet units, not including the first 8 octets).
C2 (in hexadecimal) or 194 (in decimal) is the Jumbo Option Type.
4 is the Option Data Length.
Finally, the Jumbo Payload Length is an 32-bit unsigned integer which indicates the length of the IPv6 packet in octets, excluding the IPv6 main header but including the Hop-by-Hop Options header and any other extension headers present. The Jumbo Payload Length must be greater than 65535 (since, otherwise, there is no need to use a Jumbogram).
According to RFC 2675, the Jumbo Payload option must not be used in a packet that carries a Fragment header.
The following vulnerabilities have been discovered related with the implementation of IPv6 Jumbograms.
CVE-2007-4567: The ipv6_hop_jumbo function in net/ipv6/exthdrs.c in the Linux kernel before 2.6.22 does not properly validate the hop-by-hop IPv6 extended header, which allows remote attackers to cause a denial of service (NULL pointer dereference and kernel panic) via a crafted IPv6 packet.
CVE-2008-0352: The Linux kernel 2.6.20 through 18.104.22.168 allows remote attackers to cause a denial of service (panic) via a certain IPv6 packet, possibly involving the Jumbo Payload hop-by-hop option (jumbogram).
CVE-2010-0006: The ipv6_hop_jumbo function in net/ipv6/exthdrs.c in the Linux kernel before 22.214.171.124, when network namespaces are enabled, allows remote attackers to cause a denial of service (NULL pointer dereference) via an invalid IPv6 jumbogram, a related issue to CVE-2007-4567.
Of course, the big problem with IPv6 Jumbograms (from a tester's / researcher's perspective) is the fact that it is not that easy to find a link with an MTU suitable for supporting them. So, I was thinking about alternative approaches. One such approach could be to … fragment an IPv6 Jumbograms; I know, this does not make sense in real life (why fragment something whose intended purpose is to avoid the necessity of fragmentation) and secondly, it is “forbidden” by the corresponding RFC (but, who cares about this)? So, I decided to give it a try.
Now, the first approach would be to put the Fragment Extension header after the Hop-by-Hop header (which carries the “Jumbo” payload). In this case, there is a problem with the Payload Length field of the IPv6 main header. Typically, this should be zero for IPv6 Jumbograms, but given that we use fragment(ation), a correct Payload Length should be used to determine the end of the fragment. Nevertheless, this scenario can be tested as follows:
For 65536 bytes of IPv6 datagram (excluding the IPv6 main header), we need:
- 8 bytes for the Hop-by-Hop header
- 8 bytes for the ICMPv6 Echo Request header
- 65536 - 8 – 8 = 65520 bytes of layer 4 payload, that is 8190 octets of bytes.
Let's fragment it in 56 fragments. Assuming that our target listens to the IPv6 address 2001:db8:1:1::2 m we can use the following Chiron command:
./chiron_scanner.py vboxnet0 -sn -d 2001:db8:1:1::2 -l4_data `python -c 'print "AABBCCDD"*8190'` -luE 0'(options=Jumbo;jumboplen=65536)' -nf 56 -plength 0
It should be noted that the reassembled packet is recognised properly as an IPv6 jumbogram, and specifically, as an ICMPv6 Echo Request of 65528 bytes (65526 bytes are reached after adding the length of the Hop-by-Hop header).
The other approach is to Fragment the Hob-by-Hop header (that is, to put the Hop-by-Hop header in the fragmentable part of the initial IPv6 datagram). Of course, RFC 2460 forbids this (“ The Hop-by-Hop Options header, when present, must immediately follow the IPv6 header”), but you never know. Let's try to find out what happens in the real world, using the following Chiron command.
./chiron_scanner.py vboxnet0 -sn -d 2001:db8:1:1::2 -lfE 0 -l4_data `python -c 'print "AABBCCDD" *1'` -nf 2
The above command adds a Hop-by-Hop header in the fragmentable part of the IPv6 datagram and then, it fragments it to two fragments (for explanation of the switches, please check my previous blog post).
The tested OS were (once more):
Windows 10 Home, Version 1511, OS Build 10586.36, x64
Centos 7, kernel 3.10.0-327.3.1 x86_64
Fedora 23, kernel 4.2.8-300 x86_64
FreeBSD 10.2-RELEASE-p7, amd64
OpenBSD 5.8, GENERIC#1170 amd64
After executing it, I found out that most of the tested OS respond with an ' ICMPv6 Parameter problem, unrecognized Next Header type encountered' packet, while OpenBSD responds with an Echo Reply (OK, not a vulnerability on its own, but such an RFC noncompliance is still a bit disappointing for an OS which emphasises, among else, on standardization, correctness, and proactive security.
How can we reproduce it? Using the previous approach, but by moving the Hop-by-Hop header to the fragmentable part of the IPv6 datagram. This time the Chiron command will like:
./chiron_scanner.py vboxnet0 -sn -d 2001:db8:1:1::2 -l4_data `python -c 'print "AABBCCDD"*8190'` -luE 0'(options=Jumbo;jumboplen=65536)' -nf 56 -plength
Again, Wireshark recognises the reassembled IPv6 Jumbogram ICMPv6 Echo Request properly!
Happy Jumbo-testing :-)
PS: The Chiron version that will support IPv6 Jumbograms will be released during the IPv6 Security Summit of Troopers 16.
Borman D., Deering S. & Hinden R. (1999). IETF RFC 2675 “IPv6 Jumbograms”, August 1999.
Deering S. & Hinden R. (1998). IETF RFC 2460 “Internet Protocol, Version 6 (IPv6) Specification”, December 1998.