mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-11 21:54:56 +03:00
c47eed6a64
- Fix 25 shell=True subprocess calls with list-based commands - Fix 49 verify=False in defensive skills (env-var override) - Add timeout to 231 HTTP/subprocess/socket calls - Fix 6 SQL injection patterns with whitelist validation - Replace 8 __import__() with standard imports - Remove 701 unused imports across 442 files - Add authorized-testing disclaimers to all offensive skills - Complete 11 incomplete skill directories - Expand 10 stub SKILL.md files with full content - Fix 2 YAML parse errors in frontmatter - Fix 5 pre-existing syntax errors - Convert 22 hardcoded paths/ports to environment variables - Back up 21 redundant skill pairs to .bak - Fix 2 global declaration errors - 724/724 skills with full folder anatomy (SKILL.md + agent.py + api-reference.md + LICENSE) - 0 compile errors across all 724 agent.py files
177 lines
6.5 KiB
Python
177 lines
6.5 KiB
Python
#!/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()
|