It’s a fact: Pv6 deployments are on the raise. We are close to the end of 2011 and this year was really some kind of a kick-off year to deploy the new protocol or to make live tests. I won’t come back on all the new features implemented in the sixth version of our beloved protocol but one of them is interesting amongst the others: the auto-discovery. Of course, it was already possible to let IPv4 hosts configure themselves via DHCP but here, it’s directly integrated in the stack. With IPv6, four new ICMP message types were introduced:
- Neighbor advertisement / solicitation
- Router advertisement /solicitation
Those are part of the “Neighbor discovery” as described in RFC4861. When an IPv6-enabled host connects to a network, it waits for a router advertisement packet but it can also generate some solicitation packets to discover more quickly if IPv6 routers are connected on the same wire. Once received, the router will respond and send the required information to the host to configure its IPv6 stack. One of the information is the network prefix (usually a /64) which will be used to generate IPv6 addresses. Such advertisement or solicitation messages are sent to the special address “ff02::1” which represents all the hosts connected on the wire (same behavior as a broadcast).
If you think like a blackhat, you already understood that this auto-configuration feature can be used to redirect some traffic to a rogue device behaving like an IPv6 router. Nothing new here, such attacks exist for years using a rogue DHCP server. But IPv6 has a big advantage: it can be run on companies networks even if they don’t plan to implement it in a short term. Combined with other protocols like NAT-PT (“Network Address Translation – Protocol Translation” – defined in RFC2766), it’s easy to conduct an attack. Indeed, NAT-PT can “translate” DNS lookups and will return IPv6 addresses. This will force traffic sent to IPv4 only websites to be send to the rogue router. Don’t forget that, in presence of both stacks, modern operating systems will prefer to use IPv6! Evil! If you’re interesting in such kind of attacks, there exists a tools to automate them like: fake_router6. This issue is addressed in RFC6104.
So, the basic question is: “How to protect myself?” or more precisely “How to detect rogue IPv6 routers?“. On Linux systems, the detected neighbors can be displayed using the ‘ip‘ command:
# ip -6 neigh show fe80::230:48ff:fe27:4e40 dev eth1 lladdr 00:30:48:27:4e:40 router STALE
An easy way to detect a rogue router is to “grep” your official one:
# ip -6 neigh show | grep -q fe80::230:48ff:fe27:4e40 && echo 'Rogue router detected!' Rogue router detected!
The problem: this is a passive way to detect rogue devices! Why not force routers to make them discoverable by sending RS (“Router Solicitation“) packets on the network? As a proof of concept, I wrote a Perl script which will broadcast RS packets and listen to potential router responses. If the router IP address changed or is not the expected one, if will report the problem.
My tool is called “rrhunter” and can be used with the following syntax:
# rrhunter.pl [-d] [-D] [-f] [-h] [-i device] [-l] [-N prefix/mask] [-n IP6addr] Â Â Â Â Â Â Â [-s Facility] [-t Seconds]
The available parameters are:
- “-d” enables the debug mode (increase verbosity)
- “-D” starts the script in daemon mode. It detaches from the console and checks for rogue routers in the background. Messages are sent to the local Syslog daemon.
- “-f” force the daemon to not detach from the console (run in foreground)
- “-h” displays the command syntax.
- “-i device” specifies the device to use to send/listen to packets (default: eth0)
- “-l” enables the listen mode. The first IPv6 neighbor detected will be used as the official one. Any change of the IP address will result in an error message.
- “-N prefix/mask” defines the expected IPv6 prefix returned by the IPv6 neighbor. Any change will result in an error message.
- “-n” defines the expected IPv6 neighbor
- “-s” defines the Syslog facility to use to log messages (default is “daemon”)
- “-t” defines the interval of time between two router solicitation packets
The minimum required parameter is “-n“:
# ./rrhunter.pl -n fe80::230:48ff:fe27:4e40 -d -i eth1 +++ Debug enabled. +++ Using interface eth1. +++ Running with PID 12252. Â +++ Expected IPv6 neighbor: fe80::230:48ff:fe27:4e40 +++ Listening on eth1. +++ Router Solicitation packet sent! +++ Detected IPv6 neighbor: fe80::230:48ff:fe27:4e40.
This example generated the following network traffic:
21:11:50.445138 IP6 (hlim 255, next-header ICMPv6 (58) payload length: 8) 2001:5c0:150e:a300:20c:29ff:fef5:edfd > ip6-allnodes: [icmp6 sum ok] ICMP6, router solicitation, length 8 21:11:50.446112 IP6 (hlim 255, next-header ICMPv6 (58) payload length: 64) fe80::230:48ff:fe27:4e40 > ip6-allnodes: ICMP6, router advertisement, length 64 Â Â Â hop limit 64, Flags [none], pref medium, router lifetime 1800s, reachable time 0s, retrans time 0s[ndp opt]
Now, let’s imagine that our router changed:
# ./rrhunter.pl -n fe80::230:48ff:fe27:4e40 -i eth1 Rogue IPv6 neighbor detected: fe80::230:48ff:fe27:4e41 (Expected: fe80::230:48ff:fe27:4e40).
Another interesting switch is “-N” which requires an IPv6 prefix. The script will check the assigned IPv6 address and report an error if it’s not in the expected scope:
# ./rrhunter.pl -n fe80::230:48ff:fe27:4e40 -d -N 2001:5c0:150f:a300::/64 -i eth1 +++ Debug enabled. +++ Using interface eth1. +++ Running with PID 12252. Â +++ Expected IPv6 network: 2001:5c0:150f:a300::/64 +++ Expected IPv6 neighbor: fe80::230:48ff:fe27:4e40 +++ Listening on eth1. +++ Router Solicitation packet sent! +++ Detected IPv6 neighbor: fe80::230:48ff:fe27:4e40. Unexpected IPv6 address detected: 2001:5c0:150e:a300:20c:29ff:fef5:edfd (Expected: 2001:5c0:150f:a300::/64).
Next example, we start rrhunter in listen mode:
# ./rrhunter.pl -l -D -i eth1 -f Learned IPv6 neighbor: fe80::230:48ff:fe27:4e40
Now, let’s make a Windows 7 box become a IPv6 router on the same LAN. This is easy to do from the command line:
C:\users\demo>netsh interface ipv6 add address interface="Local Area Connection" address=ff44:bbcc:bc95::1 C:\users\demo>netsh interface ipv6 set interface interface="Local Area Connection" advertise=enabled C:\users\demo>netsh interface ipv6 add route prefix=::/0 interface="Local Area Connection" nexthop=:: publish=Yes C:\users\demo>netsh interface ipv6 add route interface="Local Area Connection" prefix=fd44:bbcc:bc95::/64 publish=yes
You should see a few seconds later in the shell running the Perl script:
Rogue IPv6 neighbor detected: fe80::8d7d:7ee9:361a:e41e (Expected: fe80::230:48ff:fe27:4e40).
With ‘fe80::8d7d:7ee9:361a:e41e‘ being the IPv6 link address of the Windows 7 box.
WARNING! This is a proof of concept! By writing this script, I played with the creation and injection of IPv6 packets with Perl (quite funny). I did not test the script in a production environment. On some Linux kernels, strange messages are generated (“netlink: 4 bytes leftover after parsing attributes“). The Perl script is available “as is” on github.com. Feel free to use it, change it, improve it.
Alternatively, you could have a look at rafixd from the Kame project.