vulhub 靶场搭建实战:Docker 攻防演练

vulhub 靶场是把漏洞环境变成可重复实验的基础设施。想验证一条 WAF 规则是否真的拦得住某个漏洞,靠读文档和凭经验判断都不够,必须在能复现的环境里打一次。本文用 Docker 与 vulhub 搭建演练环境,重点不是怎么把靶场跑起来,而是怎么让它跑在隔离网段里、入口收敛、流量可回溯,避免靶场本身变成新的攻击面。

适用场景

  • 新增 WAF 或防护规则后需要验证拦截效果,又不能直接打生产站点
  • 安全培训需要真实可攻击的服务,而不是只讲幻灯片
  • 蓝队要验证检测规则能否发现某类利用行为
  • 漏洞复现与内部分享,需要一键起停的一致环境

核心前提是授权与隔离:靶场必须部署在独立主机、独立网段上,只允许演练人员访问。曾有团队把靶场容器端口映射到所有网卡,几天后被外部扫描器发现并利用,靶场直接从工具变成了入口。这类事故几乎都不是漏洞本身造成的,而是部署方式的问题。

前置条件

  • 一台独立演练主机,建议 4 核 8G 以上,磁盘 50G 起,镜像数量会快速累积
  • 已安装 Docker 与 compose v2,内核支持 bridge 网络与 iptables 过滤
  • 一台可访问该主机的跳板机,演练人员只从跳板机进入
  • 书面的演练授权范围与时间窗,明确可攻击目标与禁止动作
  • 主机为独立实例或独立磁盘,不与生产环境共用资源

原理说明:风险来自部署方式,而不是漏洞本身

vulhub 是预置漏洞环境的集合,每个漏洞对应一个 compose 文件,拉起来就是一个真实可攻击的服务。这些容器本身带着已知漏洞,防护手段不可能靠打补丁,只能靠网络层面的隔离与访问控制。因此整套方案的设计重心是三个约束:网络隔离,容器只在内部 bridge 上,没有外网出口;入口收敛,只有跳板机可达;可回溯,全程抓包,事后能复盘完整利用链。

三个约束缺任何一个,靶场都会从工具变成负担。没有隔离,被投递的反弹载荷会主动向外发起连接;没有入口收敛,靶场会被互联网扫描器当成真实目标;没有抓包,演练结束后只剩一句「打穿了」,无法转化为任何检测规则。

还有一点值得提前说明:靶场里的漏洞版本通常较老,直接拿它的利用结果去推断生产环境是否受影响并不可靠。靶场的作用是提供真实可用的 payload 与行为样本,判断生产风险仍要回到具体版本与配置上核对。

操作步骤一:建立隔离网络

用内部网络模式创建无外网出口的 bridge 网络。这样即使靶场里有容器执行了外连载荷,也无法连出去。

# 创建内部专用网络,禁用外部访问
docker network create --driver bridge \
  --subnet 172.31.240.0/24 --gateway 172.31.240.1 \
  --internal drill-net

# 确认 internal 标志已生效,输出应为 true
docker network inspect drill-net -f '{{.Internal}}'

操作步骤二:拉起靶场并收敛入口

拉取目标漏洞环境并启动。启动后先看清容器地址与端口映射,再决定是否保留宿主机端口映射——演练时优先用容器地址直连,不做宿主机端口映射就少一层暴露。

mkdir -p /opt/drill/pcap
cd /opt/vulhub/struts2/s2-045

# 指定平台,避免在 ARM 主机上拉到不兼容镜像
docker compose up -d --platform linux/amd64

# 查看容器状态与网段内地址
docker compose ps
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' \
  $(docker compose ps -q)

如果某个环境必须暴露宿主机端口,绑定地址要写成 127.0.0.1 而不是默认的全部网卡地址,再通过 SSH 隧道供跳板机访问。这一步是防止靶场被外部发现最有效的措施,成本却最低。

操作步骤三:限制访问来源并采集流量

仅靠网络隔离还不够,同一网段内的其他主机也应限制。用 Docker 的 DOCKER-USER 链做来源过滤:只放行跳板机,其余丢弃。同时开启抓包,把整个演练过程的流量落盘。

BR=br-$(docker network inspect drill-net -f '{{.Id}}' | cut -c1-12)

# 只放行跳板机 172.31.240.10,其余来源丢弃
sudo iptables -I DOCKER-USER -i $BR -s 172.31.240.10 -j RETURN
sudo iptables -I DOCKER-USER -i $BR -j DROP

# 全程抓包,供演练后做利用链复盘
sudo tcpdump -i $BR -s 0 -U \
  -w /opt/drill/pcap/s2-045-$(date +%Y%m%d%H%M).pcap

抓包文件建议按演练批次命名并保留 30 天。蓝队复盘时最有用的是请求体的原始字节,而不是界面截图——截图无法还原编码变形后的 payload,也无法用于回放验证。

操作步骤四:演练执行与防御验证

演练的目标不是打穿靶场,而是把同一条利用流量打向受保护站点,比对拦截结果。这样靶场只承担提供真实 payload 的角色,验证对象是防御设备本身。

# 1) 先在靶场确认 payload 可用(返回 200 且命令有回显)
curl -s -o /dev/null -w '%{http_code}\n' \
  --data-urlencode "redirect=%24%7B%23context%5B%27xwork.MethodAccessor.denyMethodExecution%27%5D%3Dfalse%7D" \
  http://172.31.240.21:8080/index.action

# 2) 再用同一 payload 打受保护站点,期望被拦截
curl -s -o /dev/null -w '%{http_code}\n' \
  "https://www.example.com/index.action?redirect=%24%7B%23context%7D"

两次结果的组合只有四种:靶场返回 200 且受保护站点被拦截,说明规则有效;靶场返回 200 而受保护站点也返回 200,说明规则缺失需要补;靶场返回异常,说明 payload 与目标版本不匹配,先修 payload 再谈防御。这个对照法能避免把「payload 写错」误判成「防御成功」,后者是演练中最常见也最危险的结论错误。

配置验证

演练开始前必须完成四项检查,全部通过才允许开始。

# 1) 公网不可达:从外部主机访问应超时或连接拒绝
curl -m 3 -s -o /dev/null -w '%{http_code}\n' http://PUBLIC_IP:8080/

# 2) 网络无外网出口,期望输出 NO_EGRESS
docker run --rm --network drill-net alpine sh -c \
  'wget -T3 -q -O- http://example.com >/dev/null 2>&1 || echo NO_EGRESS'

# 3) 确认没有全部网卡的端口映射
docker ps --format '{{.Names}}\t{{.Ports}}' | grep '0.0.0.0' || echo NO_PUBLIC_BIND

# 4) 演练结束彻底销毁,避免残留容器成为真实风险
docker compose down -v --remove-orphans
docker network rm drill-net
  • 公网不可达:外部访问返回 000 或连接被拒绝
  • 无外网出口:容器内访问外部域名失败
  • 无全网卡绑定:容器端口列表中不出现通配地址映射
  • 可彻底销毁:销毁后网络与数据卷清理干净,无残留容器

常见问题

Q1:靶场启动后本机能访问,跳板机却连不上?

先确认容器是否真的在隔离网络上:用 docker inspect 查看网络字段,如果显示的是默认 bridge,说明 compose 文件未指定网络,需要在 compose 中加 networks 配置或用命令行覆盖。其次检查过滤规则里的放行地址是否写成了跳板机的原始地址,跨网段时源地址会被转换,规则必须按转换后的地址写。

Q2:镜像拉取失败或容器不断重启?

多数是架构不匹配。在 ARM 主机上拉 amd64 镜像会启动即退出,用 –platform linux/amd64 指定平台,或直接换 amd64 主机。若是拉取超时,则先给 Docker 配置镜像加速地址并确认仓库可达,不要在超时上反复重试消耗演练时间窗。

Q3:演练结束后发现靶场容器还在跑,有风险吗?

有,而且是最常见的遗留问题。带漏洞的容器长期运行,等同于在环境里留了一台可被利用的主机,尤其当它不在隔离网络之内时。建议把销毁命令写进演练流程的最后一步,并用 docker ps -a 按 compose 项目标签复核残留,确认清理干净再结束演练。

总结

vulhub 靶场的价值是把验证变成可重复的动作:规则改了、检测逻辑调了、新漏洞出来了,都能在几分钟内起一套环境打一次。但前提是隔离、收敛、可回溯三个约束真正做到位,否则靶场本身就是最大的风险点。把上面的检查清单固化成脚本,演练前跑一遍、演练后再跑一遍,这套环境才能长期使用而不出事故。