#!/usr/bin/env python3 # For authorized testing in lab/CTF environments only """IPv6 vulnerability assessment agent using scapy for NDP/RA analysis.""" import argparse import json import logging import sys from datetime import datetime from typing import List try: from scapy.all import ( sniff, sendp, sr, get_if_hwaddr, get_if_addr6, conf, Ether, IPv6, ICMPv6ND_RA, ICMPv6ND_NA, ICMPv6ND_NS, ICMPv6NDOptSrcLLAddr, ICMPv6NDOptPrefixInfo, ICMPv6NDOptRDNSS, ICMPv6NDOptDstLLAddr, ) from scapy.layers.inet6 import ICMPv6EchoRequest except ImportError: sys.exit("scapy is required: pip install scapy") logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") logger = logging.getLogger(__name__) def discover_ipv6_hosts(interface: str, timeout: int = 5) -> List[dict]: """Discover IPv6 hosts by sending ICMPv6 Echo to all-nodes multicast.""" target = "ff02::1" pkt = IPv6(dst=target) / ICMPv6EchoRequest() logger.info("Sending ICMPv6 Echo to all-nodes multicast on %s", interface) replies = [] try: ans, _ = sr( pkt, iface=interface, timeout=timeout, verbose=0, multi=True ) for sent, recv in ans: src = recv[IPv6].src replies.append({"ipv6_address": src, "mac": recv.src if hasattr(recv, "src") else ""}) except Exception as exc: logger.error("Discovery failed: %s", exc) logger.info("Discovered %d IPv6 hosts", len(replies)) return replies def capture_router_advertisements(interface: str, timeout: int = 10) -> List[dict]: """Capture and analyze Router Advertisement packets on the network.""" logger.info("Capturing Router Advertisements on %s for %ds", interface, timeout) ras = [] def process_ra(pkt): if pkt.haslayer(ICMPv6ND_RA): ra_info = { "src_ip": pkt[IPv6].src, "src_mac": pkt.src if hasattr(pkt, "src") else "", "router_lifetime": pkt[ICMPv6ND_RA].routerlifetime, "managed_flag": bool(pkt[ICMPv6ND_RA].M), "other_flag": bool(pkt[ICMPv6ND_RA].O), "prefixes": [], "dns_servers": [], } if pkt.haslayer(ICMPv6NDOptPrefixInfo): layer = pkt[ICMPv6NDOptPrefixInfo] ra_info["prefixes"].append({ "prefix": layer.prefix, "prefix_len": layer.prefixlen, "valid_lifetime": layer.validlifetime, }) if pkt.haslayer(ICMPv6NDOptRDNSS): ra_info["dns_servers"] = pkt[ICMPv6NDOptRDNSS].dns ras.append(ra_info) logger.info("RA from %s (lifetime=%d)", ra_info["src_ip"], ra_info["router_lifetime"]) sniff(iface=interface, filter="icmp6", prn=process_ra, timeout=timeout, store=0) return ras def detect_rogue_ra(ras: List[dict], known_routers: List[str]) -> List[dict]: """Identify rogue Router Advertisements from unknown sources.""" rogues = [] for ra in ras: if ra["src_ip"] not in known_routers: rogues.append({ "alert": "ROGUE_ROUTER_ADVERTISEMENT", "src_ip": ra["src_ip"], "src_mac": ra["src_mac"], "router_lifetime": ra["router_lifetime"], "prefixes": ra["prefixes"], }) logger.warning("ROGUE RA detected from %s", ra["src_ip"]) return rogues def check_ipv6_firewall() -> dict: """Check if ip6tables rules are configured (Linux only).""" import subprocess result = {"ip6tables_present": False, "rules_count": 0, "rules": []} try: output = subprocess.run( ["ip6tables", "-L", "-n", "--line-numbers"], capture_output=True, text=True, timeout=5, ) lines = [l.strip() for l in output.stdout.strip().split("\n") if l.strip()] result["ip6tables_present"] = True result["rules_count"] = len([l for l in lines if l and not l.startswith("Chain") and not l.startswith("num")]) result["rules"] = lines[:20] except (FileNotFoundError, subprocess.TimeoutExpired): logger.warning("ip6tables not available") return result def check_tunnel_protocols(interface: str, timeout: int = 5) -> dict: """Check for IPv6 tunneling protocols (Teredo, 6to4, ISATAP).""" tunnels = {"teredo": False, "six_to_four": False, "isatap": False} def detect_tunnel(pkt): if pkt.haslayer("UDP") and (pkt["UDP"].sport == 3544 or pkt["UDP"].dport == 3544): tunnels["teredo"] = True if pkt.haslayer("IP") and pkt["IP"].proto == 41: tunnels["six_to_four"] = True try: sniff(iface=interface, prn=detect_tunnel, timeout=timeout, store=0) except Exception as exc: logger.warning("Tunnel detection failed: %s", exc) return tunnels def generate_assessment(interface: str, known_routers: List[str]) -> dict: """Run complete IPv6 security assessment.""" hosts = discover_ipv6_hosts(interface, timeout=5) ras = capture_router_advertisements(interface, timeout=10) rogues = detect_rogue_ra(ras, known_routers) firewall = check_ipv6_firewall() tunnels = check_tunnel_protocols(interface, timeout=5) findings = [] if rogues: findings.append(f"CRITICAL: {len(rogues)} rogue Router Advertisements detected") if firewall["rules_count"] == 0: findings.append("HIGH: No ip6tables rules configured") if tunnels["teredo"]: findings.append("MEDIUM: Teredo tunnel traffic detected") return { "assessment_date": datetime.utcnow().isoformat(), "interface": interface, "ipv6_hosts_discovered": len(hosts), "hosts": hosts, "router_advertisements": ras, "rogue_ras": rogues, "firewall_status": firewall, "tunnel_protocols": tunnels, "risk_findings": findings, } def main(): parser = argparse.ArgumentParser(description="IPv6 Vulnerability Assessment Agent") parser.add_argument("--interface", default="eth0", help="Network interface") parser.add_argument("--known-routers", nargs="*", default=[], help="Known legitimate router IPv6 addresses") parser.add_argument("--output", default="ipv6_assessment.json") args = parser.parse_args() report = generate_assessment(args.interface, args.known_routers) with open(args.output, "w") as f: json.dump(report, f, indent=2, default=str) logger.info("Report saved to %s", args.output) print(json.dumps(report, indent=2, default=str)) if __name__ == "__main__": main()