Files
IoT_PreTester_V46_Demo/django_project/analyzer/detectors/mitm.py
T
2026-08-19 08:04:59 +02:00

79 lines
3.4 KiB
Python

"""Detect local network manipulation and man-in-the-middle indicators."""
from __future__ import annotations
from collections import defaultdict
from typing import Iterable
from ..context import CaptureContext
from ..findings import Finding, Category, Severity, Confidence
from .base import BaseDetector, register
@register
class MitmDetector(BaseDetector):
name = "MitmDetector"
description = "Detects ARP spoofing, duplicate IP mappings, and ARP floods."
def detect(self, ctx: CaptureContext) -> Iterable[Finding]:
findings: list[Finding] = []
# Map each IP address to MAC addresses observed in ARP responses.
ip_to_macs: dict[str, set[str]] = defaultdict(set)
first_seen: dict[str, float] = {}
last_seen: dict[str, float] = {}
packets_for_ip: dict[str, list[int]] = defaultdict(list)
for ev in ctx.arp_events:
if ev.sender_ip and ev.sender_mac:
ip_to_macs[ev.sender_ip].add(ev.sender_mac)
if ev.timestamp is not None:
first_seen[ev.sender_ip] = min(first_seen.get(ev.sender_ip, ev.timestamp), ev.timestamp)
last_seen[ev.sender_ip] = max(last_seen.get(ev.sender_ip, ev.timestamp), ev.timestamp)
if ev.packet_number:
packets_for_ip[ev.sender_ip].append(ev.packet_number)
for ip, host in ctx.hosts.items():
if host.macs:
ip_to_macs[ip].update(host.macs)
for ip, macs in ip_to_macs.items():
if len(macs) > 1:
findings.append(Finding(
title="Multiple MAC addresses associated with one IP",
category=Category.MITM,
severity=Severity.HIGH, confidence=Confidence.MEDIUM,
source_ip=ip,
protocol="arp",
timestamp_first_seen=first_seen.get(ip),
timestamp_last_seen=last_seen.get(ip),
risk_description=(
"One IP address was advertised by multiple MAC addresses, a common "
"indicator of ARP spoofing or traffic interception."
),
recommendation=(
"Review ARP tables, enable Dynamic ARP Inspection and port security, "
"and identify the conflicting host."
),
evidence={"ip": ip, "macs": sorted(macs), "dedup_key": f"arpdup:{ip}"},
related_packets=sorted(packets_for_ip.get(ip, []))[:10],
))
# An ARP reply flood can accompany active cache poisoning.
grat = [e for e in ctx.arp_events if e.opcode == 2]
if len(grat) >= 50:
findings.append(Finding(
title="Unusually high number of ARP replies",
category=Category.MITM,
severity=Severity.MEDIUM, confidence=Confidence.LOW,
protocol="arp",
risk_description=(
"A large number of ARP replies may indicate active ARP cache poisoning."
),
recommendation="Inspect the reply source and investigate possible interception.",
evidence={"arp_reply_count": len(grat), "dedup_key": "arp_reply_flood"},
related_packets=sorted({e.packet_number for e in grat if e.packet_number})[:8],
))
return self._tag(findings)