跳转到内容

EP03:一份合法报文,为什么还能被重新利用?

一份曾经合法的数据包,如果被攻击者保存下来,过一段时间重新发送,接收方还能认出它是一份“旧数据”吗?

在前面的实验中,我们已经看到两个现象:

  • IP 通信能够正常建立,并不意味着通信过程天然安全;

  • 数据包中的 Source IP,也不能单独证明真正的发送者是谁。

但攻击者并不一定需要修改数据,也不一定需要破解通信内容。

有时候,他只需要做一件更简单的事情:

把以前截获过的一份合法报文,再发送一次。

这就是:

Replay Attack,重放攻击。

对于普通 IP 通信来说,一份过去发送过的合法报文,如果再次进入网络,看起来仍然只是一个普通的数据包。

IP 协议本身并不会自动判断:

这个数据包以前是不是已经处理过?

本实验就来验证这一点。


一、实验目标

假设 Alice 向 Bob 合法发送一条业务指令:

MSGID=EP03-001;CMD=Open Valve

Bob 第一次正常收到并处理。

随后,这份数据包被保存下来。

过一段时间以后:

Alice
没有再次发送命令

但 Eve 将之前保存的数据包重新发送出去。

我们要观察:

Bob 是否会再次收到完全相同的业务数据?

实验过程可以概括成:

Alice
  │
  │ 合法发送一次
  ▼
Bob
  │
  │ 保存历史报文
  ▼
ep03-original.pcap
  │
  │ 交给 Eve
  ▼
Kali
  │
  │ Replay
  ▼
Bob

如果 Bob 再次收到:

MSGID=EP03-001;CMD=Open Valve

那么至少可以说明:

普通 IP/UDP 通信自身并没有维护一套用来识别历史报文的防重放安全状态。


二、实验环境

完全复用 EP02 的三台虚拟机。

角色操作系统IP 地址作用
AliceRocky Linux192.168.106.130合法发送一次业务报文
Kali / EveKali Linux192.168.106.131重放历史报文
BobRocky Linux192.168.106.132接收并观察数据

实验网络仍然为:

192.168.106.0/24

拓扑:

                 VMware 实验网络
                192.168.106.0/24

        Alice          Kali / Eve          Bob
        .130              .131             .132
          │                 │                │
          │ 合法发送         │                │
          └────────────────────────────────>│
                            │                │
                            │   Replay       │
                            └───────────────>│

本实验继续遵循整个 IPSec 实验体系的原则:

在能够清楚展示机制的前提下,尽量复用已有环境,不增加额外虚拟机和网络组件。


三、为什么使用 UDP

本实验使用 UDP,而不是 TCP。

原因很简单。

TCP 自己已经包含:

Sequence Number
ACK
连接状态
重传机制
会话生命周期

如果直接使用 TCP,很容易把:

TCP 的连接状态

和:

IPSec 的防重放机制

混在一起。

UDP 更简单:

Alice
   │
   │ UDP Payload
   ▼
Bob

我们只需要观察:

一份相同的 IP/UDP 数据报再次进入 Bob 后,应用程序会不会再次收到它。


四、实验边界:我们不研究 Eve 如何截获数据

这里需要先说明一个重要边界。

三台虚拟机连接在 VMware 虚拟交换网络中。

正常情况下:

Alice → Bob

的单播数据,并不意味着旁边的 Kali 一定可以直接被动抓到。

如果为了真正实现:

Eve 截获 Alice → Bob

再加入:

ARP Spoofing
MITM
混杂模式

实验重点就会从:

Replay

变成:

如何截获数据

因此 EP03 主动把两个问题拆开。

本实验不验证:

Eve 是怎样获得这份历史报文的。

只验证:

Eve 一旦已经拥有一份合法历史报文,能不能再次利用它。

具体做法是:

Alice 正常发送
        ↓
Bob 抓包保存
        ↓
PCAP 复制给 Kali
        ↓
模拟 Eve 已经获得历史报文
        ↓
Kali Replay

这样整个实验只保留一个变量:

历史报文被重新发送。


五、确认实验网络

首先确认三台机器仍然使用 EP02 的网络配置。

Alice:

ip route get 192.168.106.132

Kali:

ip route get 192.168.106.132

Bob:

ip route get 192.168.106.130

重点确认真正使用的实验接口。

例如 Kali:

192.168.106.132 dev eth1 src 192.168.106.131

说明:

实验接口 = eth1

Alice 测试 Bob:

ping -c 3 192.168.106.132

确认通信正常。


六、Bob 启动 UDP 接收程序

Bob 上运行:

python3 -u - <<'PY'
import socket
from datetime import datetime

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

sock.bind(
    ("192.168.106.132", 9999)
)

print("Bob listening on 192.168.106.132:9999")

while True:
    data, addr = sock.recvfrom(65535)

    print(
        datetime.now().strftime("%H:%M:%S"),
        "FROM",
        f"{addr[0]}:{addr[1]}",
        "DATA:",
        data.decode(errors="replace")
    )
PY

正常情况下:

Bob listening on 192.168.106.132:9999

Bob 开始等待 UDP 数据。

如果 firewalld 阻止该端口,可以临时放行:

sudo firewall-cmd --add-port=9999/udp

实验结束后可以删除:

sudo firewall-cmd --remove-port=9999/udp

无需永久保存。


七、Bob 准备保存第一份合法报文

另外打开一个 Bob 终端。

先确认实验接口:

ip route get 192.168.106.130

假设结果为:

dev ens37

开始抓取 Alice 发来的第一份 UDP 报文:

sudo tcpdump -i ens37 -nn -s0 -c 1 \
-w /tmp/ep03-original.pcap \
'udp and src host 192.168.106.130 and dst host 192.168.106.132 and dst port 9999'

其中:

-c 1

表示:

捕获第一份符合条件的报文后自动停止。

此时 tcpdump 等待 Alice 发送。


八、Alice 合法发送一次业务指令

Alice 执行:

python3 - <<'PY'
import socket

ALICE_IP = "192.168.106.130"
BOB_IP = "192.168.106.132"

SOURCE_PORT = 40000
DESTINATION_PORT = 9999

message = b"MSGID=EP03-001;CMD=Open Valve"

sock = socket.socket(
    socket.AF_INET,
    socket.SOCK_DGRAM
)

sock.bind(
    (ALICE_IP, SOURCE_PORT)
)

sock.sendto(
    message,
    (BOB_IP, DESTINATION_PORT)
)

print("Alice sent:", message.decode())

sock.close()
PY

Alice 应该看到:

Alice sent: MSGID=EP03-001;CMD=Open Valve

这次发送固定使用:

Source IP   = 192.168.106.130
Source Port = 40000

Destination IP   = 192.168.106.132
Destination Port = 9999

这里有一个非常重要的实验条件:

Alice 只发送这一次。

后面不会再次运行这段程序。


九、Bob 第一次收到合法报文

查看 Bob 的 UDP 接收终端。

应该出现类似:

21:40:01 FROM 192.168.106.130:40000 DATA: MSGID=EP03-001;CMD=Open Valve

此时:

Alice 合法发送次数 = 1

Bob 接收次数 = 1

这是正常通信。

Alice
192.168.106.130
        │
        │ MSGID=EP03-001
        │ CMD=Open Valve
        ▼
Bob
192.168.106.132

十、检查已经保存的历史报文

由于 tcpdump 使用:

-c 1

捕获完成后应该已经自动退出。

检查:

ls -lh /tmp/ep03-original.pcap

读取:

sudo tcpdump -nn -vvv -X \
-r /tmp/ep03-original.pcap

应该可以看到:

192.168.106.130.40000
    >
192.168.106.132.9999

Payload 中还能看到:

MSGID=EP03-001;CMD=Open Valve

现在这份 PCAP 代表的是:

一份曾经真实由 Alice 发送,而且 Bob 已经接收过的合法历史报文。

这点非常重要。

我们并不是重新伪造一份看起来一样的数据。

而是直接保存原来的那一份。


十一、把历史报文交给 Eve

接下来模拟:

Eve 已经获得这份历史流量。

Bob:

sudo chmod 644 /tmp/ep03-original.pcap

Kali:

scp <Bob用户名>@192.168.106.132:/tmp/ep03-original.pcap .

确认:

ls -lh ep03-original.pcap

也可以在 Kali 读取:

tcpdump -nn -X -r ep03-original.pcap

应该再次看到:

192.168.106.130:40000
        ↓
192.168.106.132:9999

Payload:

MSGID=EP03-001;CMD=Open Valve

现在实验状态是:

Alice
已经完成一次合法发送
并且不再继续发送


Kali / Eve
已经获得:
ep03-original.pcap


Bob
已经处理过:
MSGID=EP03-001

接下来只改变一个条件:

Eve 将旧报文重新发出去。


十二、Kali 准备 Replay

确认 Scapy:

python3 -c "import scapy; print(scapy.__version__)"

如果没有:

sudo apt update
sudo apt install -y python3-scapy

Scapy 的作用不是重新构造业务内容。

而是:

读取 PCAP
    ↓
拿出原来的 IP 数据包
    ↓
重新发送

十三、Eve 重放历史报文

Kali 执行:

sudo python3 - <<'PY'
from scapy.all import rdpcap, IP, send

packets = rdpcap("ep03-original.pcap")

pkt = packets[0]

if IP not in pkt:
    raise RuntimeError("No IPv4 packet found")

historical_packet = pkt[IP]

print("Eve is replaying:")
print(historical_packet.summary())

send(
    historical_packet,
    verbose=False
)

print("Replay finished")
PY

这里值得特别注意:

Kali 没有重新输入:

MSGID=EP03-001;CMD=Open Valve

Alice 也没有再次运行发送程序。

真正发生的是:

ep03-original.pcap
        ↓
读取历史报文
        ↓
提取原来的 IP Packet
        ↓
重新发送

也就是:

Capture
   ↓
Store
   ↓
Replay

这才是本实验真正要观察的动作。


十四、Bob 再次收到相同业务数据

回到 Bob 的 UDP 接收终端。

第一次已经存在:

21:40:01 FROM 192.168.106.130:40000 DATA: MSGID=EP03-001;CMD=Open Valve

Kali 执行 Replay 后,如果 Bob 又出现:

21:40:25 FROM 192.168.106.130:40000 DATA: MSGID=EP03-001;CMD=Open Valve

那么实验已经成功。

Bob 最终看到:

21:40:01
FROM 192.168.106.130:40000
DATA: MSGID=EP03-001;CMD=Open Valve


21:40:25
FROM 192.168.106.130:40000
DATA: MSGID=EP03-001;CMD=Open Valve

两份数据拥有完全相同的:

Source IP

192.168.106.130

相同的:

Source Port

40000

相同的:

MSGID

EP03-001

甚至相同的:

CMD

Open Valve

但它们是在两个不同时间进入 Bob 的。

更重要的是:

第二份业务数据根本不是 Alice 再次产生的。

它来自 Kali 对历史数据包的重放。


十五、把实验结果放在一起看

整个过程实际上只有三步。

第一步:

Alice
        │
        │ 合法报文
        ▼
Bob

MSGID=EP03-001
CMD=Open Valve

第二步:

合法报文
    ↓
保存为:
ep03-original.pcap

第三步:

Kali / Eve
        │
        │ Replay 历史报文
        ▼
Bob

MSGID=EP03-001
CMD=Open Valve

最终:

Alice 真正发送:

1 次

但:

Bob 收到相同业务数据:

2 次

这就是整个 EP03 最关键的实验结果。


十六、为什么 Bob 没有自动认出“这是一份旧报文”

我们故意在 Payload 中加入:

MSGID=EP03-001

从人的角度看:

第一次:

MSGID=EP03-001


第二次:

MSGID=EP03-001

显然值得怀疑。

但问题是:

IP 协议不知道 MSGID 是什么。

在 IP 看来:

MSGID=EP03-001

只是 Payload 中的一串字节。

普通 IP 协议不会自动建立:

已经收到 EP03-001
        ↓
保存状态
        ↓
再次收到 EP03-001
        ↓
识别为 Replay
        ↓
Drop

Bob 当前运行的简单 UDP 应用同样没有实现这样的业务状态。

所以第二份数据进入 Bob 后:

IP Packet
    ↓
UDP
    ↓
9999 端口
    ↓
应用程序

仍然会被正常交付。


十七、这不是在证明“UDP 不安全”

这里必须控制实验结论。

本实验不能简单总结成:

UDP 不支持防重放,所以 UDP 不安全。

更准确的说法应该是:

普通 IP/UDP 通信自身没有提供类似 IPSec 的通用防重放安全状态。

应用程序当然可以自己实现防重放。

例如设计:

Message ID
Timestamp
Nonce
Transaction ID
Session State

假设应用第一次处理:

MSGID=EP03-001

以后保存:

EP03-001
=
Already Processed

第二次再次出现:

EP03-001
        ↓
已经处理
        ↓
Reject

完全可以。

但这个机制属于:

应用层自己设计的防重放逻辑。

不是普通 IP 协议自动提供的。


十八、重放与“用户主动执行两次”不是一回事

还有一个容易混淆的问题。

假设合法用户真的执行了两次:

Open Valve

这是不是攻击?

不一定。

例如:

第一次业务请求
        ↓
合法

第二次用户主动操作
        ↓
也可能合法

这是业务系统需要判断的问题。

Replay 关注的是另一种情况:

合法用户只发送一次
        ↓
攻击者捕获历史数据
        ↓
攻击者重新利用

所以:

防重放不是简单地禁止两个相同字符串出现。

真正需要判断的是:

这份受保护的报文是不是以前已经被接收和处理过。

这也是后面 IPSec 防重放机制需要解决的问题。


十九、为什么安全通信需要专门的防重放状态

现在我们还没有使用 IPSec。

但通过 EP03 已经能够提前推导出一个需求:

即使以后做到:

攻击者看不懂数据

甚至:

攻击者不能修改数据

如果攻击者仍然可以:

捕获一份合法报文
        ↓
保存
        ↓
原样重新发送

那么安全问题依然存在。

这意味着真正的安全通信除了:

机密性
完整性
身份认证

还需要:

防重放

也就是说,接收方必须拥有某种状态,用来判断:

这个受保护的数据包

以前是否已经接收过?

至于 IPSec 具体怎么做到这一点,现在还不展开。

等进入 ESP 后,可以重新设计一个对应实验。

届时就可以形成非常直观的对照。


二十、普通通信与 IPSec 的对照

EP03 当前验证的是:

普通 IP / UDP

合法报文
    ↓
Capture
    ↓
Store
    ↓
Replay
    ↓
Bob 再次接收

后面进入 ESP 后,则可以验证:

IPSec ESP

合法 ESP 报文
    ↓
Capture
    ↓
Store
    ↓
Replay
    ↓
Anti-Replay Check
    ↓
识别历史报文
    ↓
Drop

这时读者再看到:

Sequence Number
Replay Window

就不会觉得它们只是突然出现的协议字段。

而会知道:

这些机制是为了处理 EP03 中已经真实观察到的问题。


实验总结

本实验没有破解通信,没有修改 Payload,也没有重新生成一条新的业务命令。

只做了三件事情:

Alice 合法发送一次
        ↓
保存历史报文
        ↓
Kali 重放历史报文

最终:

Alice 合法发送次数:

1

而:

Bob 接收到:

MSGID=EP03-001;CMD=Open Valve

2 次

第二次并不是 Alice 再次产生的业务请求。

而是历史报文被重新利用。

因此我们验证了:

一份报文曾经合法,并不意味着它在未来再次出现时仍然应该被信任。

普通 IP 协议负责:

把数据包送到目的地

但它本身并不负责:

判断这个数据包
是不是以前已经处理过的历史报文

因此:

能够正常接收数据
        ≠
能够识别 Replay

或者更容易记住:

普通 IP 只知道“这个数据包现在到了”,并不知道“这个数据包以前是不是来过”。

而这,正是安全通信还必须额外解决的另一个问题:

防重放。