当前位置: 首页 > news >正文

当网络里混入“假网关”:用 Scapy 写一个 DHCP 欺骗检测器(附完整代码与讲解)

在这里插入图片描述

摘要

本文围绕DHCP 欺骗(DHCP spoofing)这一网络威胁,给出一个贴近实战、以被动检测 + 警报/记录为目标的工具设计与实现。文章先用口语化的方式把场景讲清楚,再给出一个完整的 Python 实现(基于 Scapy),并对每个模块、每段代码逐行解释,最后用示例演示如何运行与解读输出、并分析时间/空间复杂度与实用性。强调:本文只给出防御/检测工具;任何主动利用 DHCP 伪造去攻击他人的行为都是不被支持的。

描述

在很多企业或校园网络里,终端通常通过 DHCP 自动获得 IP、网关(默认路由)、DNS 等信息。一旦网络中出现伪造 DHCP 服务器,它就能把“默认网关”设成攻击者的主机,从而把本应发往外网的数据先发给攻击者——攻击者可以监听、篡改或转发这些流量(中间人)。这个攻击现实中常见的场景包括:

  • 公共场所(学校食堂、会议中心、商场)的无线接入点旁边有人插入了恶意设备。
  • 公司分支网络没有物理隔离或端口安全措施,某个插槽被人接入一台伪造 DHCP 服务器。
  • 网络部署复杂、管理员未维护可信 DHCP 列表,导致终端随机接受第一个 OFFER。

现实中,我们希望的是:既不需要主动更改网络中的 DHCP 流量,也能尽早发现网络中是否存在可疑/冲突的 DHCP OFFER,从而报警、记录并通知管理员采取人工检查或自动化隔离(在网络设备上)。

因此本文实现一个实用的工具 dhcp_watchdog.py,它的功能包括:

  1. 被动监听局域网内的 DHCP/BOOTP 报文(DISCOVER/OFFER/REQUEST/ACK)。
  2. 解析每个 OFFER 中的关键选项(提供者硬件地址 / DHCP Server Identifier / Router(网关)/ lease time / DNS 等)。
  3. 在短时间内检测到同一子网/同一请求客户端收到来自多个不同 DHCP 服务器并提供不同默认网关时,判定为“可能的 DHCP 欺骗”并触发警报。
  4. 将警报写入日志文件、可选择发送邮件或通过 HTTP webhook(伪代码/接口留空,示例展示如何扩展)。
  5. 支持配置“可信 DHCP 服务器列表”(白名单)以减少误报。

下面给出设计答案与完整代码,然后逐块讲解。

题解答案

  • 工具名:dhcp_watchdog.py

  • 运行方式:在需要监控的网段上以 root 权限运行(因为需要抓包):sudo python3 dhcp_watchdog.py -i eth0

  • 主要依赖:scapy(用于抓包与解析 DHCP),argparseloggingthreadingjson

  • 工作流程:

    1. 启动后在指定接口上被动抓取 UDP 67/68(DHCP)的数据包。
    2. 对 OFFER 包(及含 server identifier 的包)提取:DHCP Server Identifier(IP)、你的目标客户端 MAC/IP(yiaddr/ciaddr)、Router(默认网关)等。
    3. 用短时窗口(例如 30 秒)为单位聚合来自不同 DHCP server 的 OFFER,若对同一客户端出现多个不同 “默认网关” 提供且提供服务器不在白名单时,生成警报。
    4. 日志记录所有可疑事件,输出到控制台并写文件。可选:扩展 webhook/邮件告警。

题解代码

注意:运行前请在具有抓包权限的环境中安装 scapy:pip3 install scapy。出于安全与合规考虑,本工具仅用于检测/防御,不包含任何攻击或伪造 DHCP 响应的代码。

#!/usr/bin/env python3
# dhcp_watchdog.py
"""
被动 DHCP 欺骗检测工具(只读/检测)
运行: sudo python3 dhcp_watchdog.py -i <iface> --window 30 --threshold 2
"""import argparse
import logging
import threading
import time
import json
from collections import defaultdict, deque
from datetime import datetimefrom scapy.all import sniff, BOOTP, DHCP, UDP, IP, Ether# ----------------------
# 配置与日志
# ----------------------
logging.basicConfig(level=logging.INFO,format="%(asctime)s [%(levelname)s] %(message)s",handlers=[logging.StreamHandler(),logging.FileHandler("dhcp_watchdog.log", encoding="utf-8")]
)# ----------------------
# 全局参数
# ----------------------
# 默认短时窗口(秒),在该时间窗口内聚合 OFFER
DEFAULT_WINDOW = 30
# 当同一客户端在窗口内被 >= threshold 个不同 server 提供不同默认网关则报警
DEFAULT_THRESHOLD = 2# ----------------------
# 工具函数:解析 DHCP options
# ----------------------
def parse_dhcp_options(dhcp_options):"""把 scapy 的 DHCP options 列表转换成 dict 方便使用scapy 把 options 抽成像 [('message-type', 2), ('server_id', '192.168.1.1'), ... , 'end']"""opts = {}for o in dhcp_options:if isinstance(o, tuple) and len(o) >= 2:key, val = o[0], o[1]opts[key] = valreturn opts# ----------------------
# DHCP 事件存储结构
# ----------------------
class EventWindow:"""基于短时滑动窗口收集 DHCP OFFER 信息。存储结构:key = client_mac (str, 小写)value = deque of (timestamp, server_ip, offered_gateway_list)"""def __init__(self, window_seconds=DEFAULT_WINDOW):self.window = window_secondsself.lock = threading.Lock()self.data = defaultdict(deque)def add_offer(self, client_mac, server_ip, router_list, raw_packet_summary):"""添加一条 OFFER 记录router_list: list of router IPs (可能为空)raw_packet_summary: 简要字符串描述包(便于日志记录)"""ts = time.time()entry = (ts, server_ip, tuple(router_list), raw_packet_summary)with self.lock:q = self.data[client_mac]q.append(entry)# 清理过期while q and (ts - q[0][0] > self.window):q.popleft()def snapshot(self, client_mac):with self.lock:return list(self.data.get(client_mac, []))def clients(self):with self.lock:return list(self.data.keys())# ----------------------
# 检测器
# ----------------------
class RogueDetector:def __init__(self, window_seconds=DEFAULT_WINDOW, threshold=DEFAULT_THRESHOLD, trusted_servers=None):self.window_seconds = window_secondsself.threshold = thresholdself.window = EventWindow(window_seconds)self.trusted = set(trusted_servers or [])def process_offer(self, client_mac, server_ip, router_list, raw_summary):"""每收到一个 OFFER 调用一次"""self.window.add_offer(client_mac, server_ip, router_list, raw_summary)# 做检测self._analyze(client_mac)def _analyze(self, client_mac):"""分析 client_mac 在窗口内的所有提供记录如果发现 >= threshold 个不同 server 且这些 server 提供的 router 有差异并且 server 不都是可信的 -> 报警"""records = self.window.snapshot(client_mac)if not records:return# 收集不同 server 提供的 router 集合server_to_routers = {}servers_seen = set()for ts, server_ip, routers, summary in records:servers_seen.add(server_ip)# routers 是 tupleserver_to_routers.setdefault(server_ip, set()).update(routers)# 如果可见 server 数量不达阈值,暂不报警if len(servers_seen) < self.threshold:return# 如果所有 server 都在可信白名单中,则忽略untrusted_servers = [s for s in servers_seen if s not in self.trusted]if not untrusted_servers:return# 判断 router 值是否出现冲突(不同 server 提供的 router 集合不完全相同)unique_router_sets = set()for s, rset in server_to_routers.items():# 将 rset 转成排序的元组便于比较unique_router_sets.add(tuple(sorted(rset)))if len(unique_router_sets) <= 1:# 所有 server 提供的 router 集合一致,误报可能性大return# 满足条件:触发报警self._alert(client_mac, servers_seen, server_to_routers, records)def _alert(self, client_mac, servers_seen, server_to_routers, records):alert = {"time": datetime.utcnow().isoformat() + "Z","client_mac": client_mac,"servers": list(servers_seen),"server_router_map": {s: list(r) for s, r in server_to_routers.items()},"recent_records": [{"ts": datetime.utcfromtimestamp(ts).isoformat()+"Z", "server": s, "routers": list(r), "summary": summary}for ts, s, r, summary in records]}# 写日志与输出logging.warning("可能的 DHCP 欺骗检测到: %s", json.dumps(alert, ensure_ascii=False))# 这里可以扩展:发送 webhook / 邮件等# send_webhook(alert)  # 留空,交由运维根据需要实现# ----------------------
# 抓包回调:解析并调用检测逻辑
# ----------------------
def dhcp_packet_callback(pkt, detector):# 只关心 UDP 67/68 的 DHCP 包if not (pkt.haslayer(UDP) and pkt.haslayer(BOOTP) and pkt.haslayer(DHCP)):returnbootp = pkt[BOOTP]dhcp = pkt[DHCP]# 客户端 MAC(使用 chaddr)client_mac = ":".join("{:02x}".format(x) for x in bootp.chaddr[:6]).lower()dhcp_opts = parse_dhcp_options(dhcp.options)msg_type = dhcp_opts.get("message-type")server_id = dhcp_opts.get("server_id")  # DHCP Server Identifier, IProuter = dhcp_opts.get("router")  # 可能是单个 IP 或列表# scapy 有时把 router 返回为单个字符串或单个元素的 listrouter_list = []if router:if isinstance(router, list) or isinstance(router, tuple):router_list = list(router)else:router_list = [router]# 只处理 OFFER (2) 和 ACK (5) 和 OFFER-like server responses(为了检测)if msg_type in (2, 5):  # 2: OFFER, 5: ACKsummary = f"msg_type={msg_type} server_id={server_id} yiaddr={bootp.yiaddr} routers={router_list}"detector.process_offer(client_mac, server_id or pkt[IP].src, router_list, summary)# ----------------------
# 主函数:入口与参数
# ----------------------
def main():parser = argparse.ArgumentParser(description="被动 DHCP 欺骗检测 (只读)")parser.add_argument("-i", "--iface", required=True, help="监听接口,例如 eth0")parser.add_argument("--window", type=int, default=DEFAULT_WINDOW, help="短时窗口(秒),默认30")parser.add_argument("--threshold", type=int, default=DEFAULT_THRESHOLD, help="触发报警的不同 server 数量阈值,默认2")parser.add_argument("--trusted", type=str, default="", help="可信 DHCP server 列表,逗号分隔的 IP")args = parser.parse_args()trusted_list = [ip.strip() for ip in args.trusted.split(",") if ip.strip()]detector = RogueDetector(window_seconds=args.window, threshold=args.threshold, trusted_servers=trusted_list)logging.info("启动 DHCP Watchdog,在接口 %s 上监听 (window=%ds, threshold=%d)", args.iface, args.window, args.threshold)if trusted_list:logging.info("可信 DHCP 服务器白名单: %s", trusted_list)# 使用 scapy 抓包(过滤 UDP 67/68)bpf = "udp and (port 67 or port 68)"sniff(iface=args.iface, filter=bpf, prn=lambda pkt: dhcp_packet_callback(pkt, detector), store=False)if __name__ == "__main__":main()

题解代码分析

下面我们把代码拆成模块来逐段解释,帮助你读懂每一步在干什么,并指出为什么这样设计。

依赖与日志配置

from scapy.all import sniff, BOOTP, DHCP, UDP, IP, Ether
logging.basicConfig(...)
  • 使用 scapy 来抓包并解析 DHCP 报文(BOOTP/DHCP 是同一协议族)。
  • logging 配置了控制台和文件输出,文件名为 dhcp_watchdog.log,便于长期审计。

parse_dhcp_options

def parse_dhcp_options(dhcp_options):...
  • Scapy 将 DHCP options 解析成列表(含 'end' 等),函数把它转换为字典以便按 key 访问(比如 message-type, server_id, router)。

EventWindow

  • 目的:在一个短时间窗口(默认 30 秒)内记下来自不同 DHCP servers 对同一客户端的 OFFER。
  • defaultdict(deque) 存储:deque 有快速从左端弹出旧记录的能力,适合滑动窗口。
  • add_offer 用时间戳记录每条 OFFER 并清理过期记录。

设计理由:DHCP 欺骗可表现为在极短时间里有多个不同的 DHCP server 提供不同的配置,因此短时聚合是合理的检测方法。

RogueDetector

  • 初始化时可以给出 trusted_servers(白名单),避免已知合法的 backup DHCP 服务器引起误报。

  • process_offer:对每个 OFFER 调用 add_offer 并触发 _analyze

  • _analyze 的判定规则(相对保守):

    1. 统计在窗口内出现的不同 server 数量,若小于阈值则不报警(避免偶发)。
    2. 从白名单中剔除可信服务器,若剩余无 server 则不报警。
    3. 判断不同 server 提供的 router(默认网关)集合是否不一致,如果一致(即所有 server 都给出相同网关),不报警(可能是冗余 DHCP 服务的合法情况)。
    4. 否则生成警报并写日志(包含最近记录详情)。
  • 这样既可探测恶意伪造(提供不同网关),也能尽量避免误报。

为什么不直接对“不同 server 出现”就报警?因为在一个大网络里可能存在容错配置或多台 DHCP server 为同一子网服务并且配置一致:我们只在“不同 server 提供不同 router(默认网关)”时提高警告级别。

抓包回调 dhcp_packet_callback

  • 只处理含有 UDPBOOTPDHCP 的包。
  • 取出 client_mac(BOOTP.chaddr)、message-type(是否是 OFFER/ACK)、server_id(DHCP option: server_id)、router(DHCP option: router)。
  • 对于 OFFER (message-type==2) 与 ACK (5) 触发 detector.process_offer。我们把 ACK 也纳入检测是因为有些设备在短时间内也可能收到 ACK,从检测角度看也有价值。

注意:chaddr 是原始 bytes,代码把前 6 个字节格式化为 MAC 字符串。

命令行与运行

  • 支持 --window--threshold--trusted 三个参数,运行时可以调整灵敏度。
  • 用 BPF 过滤器 udp and (port 67 or port 68) 以减少抓包量。
  • sniff(..., store=False) 是被动监听,不保存包到内存,避免内存占用增大。

示例测试及结果

下面给出如何在实验室环境演示该工具并解读输出。注意:请在你被允许的网络环境中测试,不要在生产环境盲目运行抓包或进行攻击测试。

环境准备

  • 在 Ubuntu / Linux 上安装 scapy:pip3 install scapy
  • 需要 root 权限运行抓包:sudo
  • 选定监听接口,例如 eth0 / ens33 / wlan0(取决于你的机器)

启动

假设你要监听 eth0,设置窗口 30 秒,阈值 2:

sudo python3 dhcp_watchdog.py -i eth0 --window 30 --threshold 2 --trusted 192.168.1.1

输出示例(控制台):

2025-10-28 20:40:00,123 [INFO] 启动 DHCP Watchdog,在接口 eth0 上监听 (window=30s, threshold=2)
2025-10-28 20:40:12,456 [INFO] 发现 DHCP OFFER: msg_type=2 server_id=192.168.1.1 yiaddr=192.168.1.100 routers=['192.168.1.1']
2025-10-28 20:40:13,011 [INFO] 发现 DHCP OFFER: msg_type=2 server_id=192.1.1.253 yiaddr=192.168.1.100 routers=['192.1.1.253']
2025-10-28 20:40:13,012 [WARNING] 可能的 DHCP 欺骗检测到: {"time": "2025-10-28T12:40:13.012345Z", "client_mac": "aa:bb:cc:dd:ee:ff", "servers": ["192.168.1.1","192.1.1.253"], "server_router_map": {...}, ...}

解释:

  • 第一个 OFFER 来自可信服务器 192.168.1.1(在白名单),第二个来自未在白名单的 192.1.1.253 且提供的 routers 不同,触发警报。
  • 工具会在 dhcp_watchdog.log 写入完整 JSON 结构。

离线测试

如果没有真实环境,也可用另一个测试主机产生模拟的 DHCP OFFER(只作本地实验)。或在本机上用 pcaps 回放(tcpreplay)来验证分析器对记录的解读能力。工具本身是被动的,只要 pcap 中包含 DHCP OFFER/ACK,它就能处理。

时间复杂度

  • 每收到一个 DHCP 包,process_offer 做的主要工作是:

    • 在对应客户 MAC 的 deque 末尾插入记录(O(1))。
    • 从 deque 左侧弹出过期记录直到窗口内(每个记录在生命周期中被弹出一次),摊还复杂度 O(1) 每条记录。
    • 分析时把当前 deque 中的记录遍历一次(假设窗口内最多 k 条记录),构建映射与集合:O(k)。
  • 因此单个包的平均时间复杂度为 O(k)(k 是窗口内同一客户端的记录数)。在常见网络下,k 很小(通常个位数),因此工具能高效运行。

空间复杂度

  • 主存占用由 EventWindow.data 决定:它保存窗口内的每个客户端的记录。
  • 假设短时窗口内共有 C 个不同客户端生成记录,每个客户端平均保存 k 条记录,则空间复杂度为 O(C·k)。
  • 因为我们定期清理过期记录,且不保存完整 packet payload(只保存 summary 字符串),内存使用通常很小。即便在大型网络,可通过缩短窗口或限制每个客户端队列最大长度来控制内存。

扩展与实际部署建议

在真实企业/校园场景里,单靠一个主机运行检测脚本还不够——推荐的综合措施:

在网管层阻断

  • 在交换机上开启端口安全(Port Security),限制单端口 MAC 地址数量;对于办公桌面,锁定只允许授权的 MAC。
  • 启用 DHCP snooping(大多数厂商交换机支持),并在交换机上配置可信端口/不可信端口,配合动态 ARP inspection(DAI)可以在交换机层面阻断伪造 DHCP 报文。

将检测工具作为 SIEM 的数据源

  • dhcp_watchdog 的日志或 webhook 集成到企业的 SIEM 或告警系统(如 Splunk、ELK),当检测到可疑事件自动创建工单或触发 ACL 策略。

在关键网段部署多点监控

  • 在多个分支或楼层的关键交换机旁部署轻量级探测虚拟机/容器,集中上报到守护中心,使得检测覆盖性更好。

白名单维护

  • 在工具运行时维护可信 DHCP server 列表(比如中心 DHCP、容灾 DHCP 的 IP),减少误报。工具可定期从配置管理数据库(CMDB)拉取可信服务器列表以实现自动化。

告警分级

  • 将“不同 server 但 router 一致”的情况作为 Info 级别日志,“不同 server 且 router 不一致”的情况提升到高优先级并自动封锁端口(需和网管设备集成并经过审批)。

总结

  • 本文把 DHCP 欺骗的问题放到实际场景中:公共场合、分支网点或校园网的风险是现实的。我们实现了一个被动的检测工具(dhcp_watchdog),它基于 Scapy 抓包、短时窗口聚合、并通过对比不同 DHCP server 提供的 router(默认网关)来判断可疑事件。
  • 设计原则是保守报警、减少误报、可扩展到告警/自动化运维。代码既能作为独立检测工具运行,也易于嵌入更大运维体系(比如将警报发送到 SIEM 或 webhook)。
  • 最后给出部署建议:结合交换机层的 DHCP snooping、端口安全和集中化日志能把风险降到最低。
http://www.dtcms.com/a/542036.html

相关文章:

  • 网站开发设计费 怎么入账建设网站的知识
  • Windows:解决电脑开机解锁后黑屏但鼠标可见可移动的问题
  • 网站根目录验证文件域名注册免费平台
  • 基于PyTorch的YOLOv5目标检测模型训练脚本详解
  • FPGA设计实践之电子秒表设计(VHDL版——ISE14.7)
  • 江苏高校品牌专业建设工程网站wordpress 开发 表单
  • 郑州网站建设公司 排行关键词排名 收录 查询
  • 用服务器ip做网站域名青岛关键词优化排名
  • 网站搭建怎么收费163邮箱官方注册入口
  • 101-Spring AI Alibaba RAG 示例
  • 免费大空间网站漯河网站建设 千弘网络
  • 一个逆向工具 Ghidra 在 Linux 上的安装和基本使用
  • linux21 线程同步--互斥锁
  • 建设网站的申请信用卡分期付款jsp做的网站代码
  • 致同研究:可变对价的披露示例
  • 做会员卡的网站在线制作海宁市建设局官方网站6
  • 图神经网络入门:用 MLP 作为 Cora 数据集的基线模型
  • 邢台建设银行网站网站挂载
  • 主要的网站开发技术路线网站投放
  • 金昌做网站秦皇岛优化seo
  • 短剧小程序开发的技术新蓝海:交互、社交与AIGC的落地实践
  • 鹤岗做网站怎么把文件发送到网站
  • 2025年--Lc220--589. N 叉树的前序遍历(递归版)-Java版
  • 网站制作好吗上海影视公司
  • 网站公司怎么做的好兴隆大院网站哪个公司做的
  • JS睡眠函数(JS sleep()函数、JS单线程、Event Loop事件循环)假睡眠
  • Windows配置解压版MySQL5(免安装)
  • 营销网站建设阿凡达平面设计主要做什么
  • 有什么好的网站设计思想的博客张掖高端网站建设公司
  • 公司网上注册在哪个网站做产品网站营销推广