When my friend Didier Stevens contacted me last year to help him with a BruCON 5×5 project, I simply could not decline! Didier developed a framework to perform forensic investigations on Cisco routers. His framework is called NAFT (“Network Appliance Forensic Toolkit”). It is written in Python and provides a good toolbox to extract juicy information from routers memory. From a development point of view, the framework was ready but Didier has the great idea to prepare a workshop to train student to analyze router memory images. The 5×5 project was accepted and thanks to the support of BruCON, it was possible to buy a bunch of Cisco routers to let students play with them. Why hardware routers and not simply a virtual lab (after all we are living in the virtualisation era)? For two main reasons: To avoid licensing issues and a virtual lab does not offer the ROMMON feature which is very useful to take a memory image of the router. The very first workshop was given last week during BruCON as a first premiere. With a fully booked room of people (40), it was a success and we already got good feedbacks. But not all people are able to attend security conferences and workshops, that’s why Didier had the idea to implement an online lab where registered people could perform the same investigations as in the live workshop. That’s where I was involved in the project!
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.
There was an interesting thread on the firstname.lastname@example.org mailing list a few days ago. A member asked how to detect illegal or “rogue” gateways in a big international organization. Rogue devices can be seen from different point of views. For the network administrators or the security auditors, it’s really a pain. Users may connect prohibited devices on the network. Those are often poorly or even not configured at all (running with the factory settings). But from an attacker point of view, it’s a great opportunity to bypass controls in place like firewalls or IDS. Rogue devices are not a new threat. Already in 2008, I wrote a Nagios plug-in to detect rogue DHCP servers (also extremely dangerous in a LAN).
For evident security reasons, a network must have only one default gateway. This single network point is the way out to the Internet and must be configured to enforce security rules and control the traffic. If a rogue gateway is available, users can change their default gateway in their TCP/IP stack and they will evade all the security filters in place. A good example is a small ADSL SO-HO router connected on a corporate LAN. But a server or a workstation can also act as a gateway (a badly configuration workstation with a Tethered iPhone is very risky).
Basic detection can be achieved by using scanners like Nmap. Based on the MAC addresses or their fingerprints, major devices like Cisco or Juniper routers can be easily detected. Using SNMP, some OID’s may reveal sensible devices. But gateways can be hardened to not answer to ICMP requests or have SNMP disabled (or properly protected). To probe for rogue gateways, we need to test their routing capabilities using tools like traceroute.
Testing a big network is a pain and requires a lot of time. I wrote a Perl script to automate the following task: For every IP address in a given range, a static route for an external host is created pointing to the current scanned IP and a traceroute is performed. The results are analyzed and produce an output like in the example below. A valid gateway will report several hops.
# ./roguegw.pl -h Usage: roguegw.pl --startip x.x.x.x --endip x.x.x.x --target x.x.x.x \ [--randomize] [--help] startip / endip : Define the range of IP addresses to scan. target : Defines the target IP address to reach via the gateway. (Can be a public IP address or another organization subnet. randomize : Randomizes the IP range (usefull to work below the radar). # ./roguegw.pl --startip 192.168.254.1 --endip 192.168.254.10 Scanning IP range: 192.168.254.1 - 192.168.254.10 ... Testing: 192.168.254.1... Found 5 hops! 1: 192.168.254.1 (0.531ms) 2: * 3: 18.104.22.168 (29.207ms) 4: 22.214.171.124 (37.407ms) 5: 126.96.36.199 (45.358ms) Testing: 192.168.254.2... Host reported unreachable from 192.168.254.229 Testing: 192.168.254.3... Host reported unreachable from 192.168.254.229 Testing: 192.168.254.4... Host reported unreachable from 192.168.254.229 Testing: 192.168.254.5... Host reported unreachable from 192.168.254.229 Testing: 192.168.254.6... Host up but no packet forwarding. Testing: 192.168.254.7... Host reported unreachable from 192.168.254.229 Testing: 192.168.254.8... Host reported unreachable from 192.168.254.229 Testing: 192.168.254.9... Host reported unreachable from 192.168.254.229 Testing: 192.168.254.10... Host reported unreachable from 192.168.254.229 Done.
The script accepts some arguments: an IP range delimited by a starting and ending addresses and an optional target IP address (by default an IP address of www.google.com is used). The target can be a public IP address (on the Internet) or another private IP address. If the argument “–randomize” is given, the IP range will be first randomized. This can be helpful to work “below the radar”. Note that the script limits the traceroute to the first five hops for speed reason (it should be sufficient in most cases). To execute the script you need the following Perl modules installed: Net::IP and Net::Traceroute.
My first idea was to grab the TCP/IP settings from the host running the Perl scripts but it looks to be difficult due to the high number of systems running Perl. By providing an IP range you can restrict the scan to only a few IP addresses like to ten first or the ten last of your subnet (where are usually assigned gateway IP addresses). This will also be less intrusive.
The Perl script is available here: roguegw.pl.txt. Same disclaimer as usual, it is provided “as is” without any warranty. Feel free to re-use it or adapt it.