目 录
[1.1 只读盘点](#1.1 只读盘点)
[1.2 端口规划](#1.2 端口规划)
[二、编写 docker-compose.yml](#二、编写 docker-compose.yml)
[坑 1:容器内 200,宿主机却连接被重置](#坑 1:容器内 200,宿主机却连接被重置)
[坑 2:Mutillidae 提示 Database Offline](#坑 2:Mutillidae 提示 Database Offline)
[坑 3:DVNA 容器启动即退](#坑 3:DVNA 容器启动即退)
[坑 4:大镜像拉取中途 unexpected EOF](#坑 4:大镜像拉取中途 unexpected EOF)
[6.1 初始化速查(哪些靶场要「先点一次」)](#6.1 初始化速查(哪些靶场要「先点一次」))
[6.2 纪律提醒](#6.2 纪律提醒)



一、规划先行:资源盘点与端口规划
第 0 篇说过,靶场宿主是一台 Rocky Linux 9.8 虚拟机(10.10.10.240,主机名 pxg),Docker 承载 15 个常驻靶场容器。动手之前先做只读盘点,确认三件事:系统与 Docker 版本、资源余量、端口占用。
1.1 只读盘点
cat /etc/rocky-release # 确认系统版本
docker --version && docker compose version
free -h # 内存余量,决定能跑多少容器
df -h / # 磁盘余量,镜像很占空间
ss -tlnp # 已占用端口,避免冲突
盘点结论直接决定方案:2C/8G 机器跑 15 个轻量 Web 容器绰绰有余(实测内存占用约 4G),但 Metasploitable2 的老服务与 Vulhub 大镜像要按需管理。
1.2 端口规划
端口规划是本篇最重要的设计决策:全部 Web 靶场集中在 8081~8091,8090 留给导航门户;数据库靶标用 3307/3308/6380;Metasploitable2 的 12 个服务端口全部非标准映射,刻意避开宿主机自己的 22 和 80。
|-----------------|-----------------|----------------------------------------------------------------------------------|
| 端口段 | 用途 | 说明 |
| 8081~8089、8091 | Web 靶场 | DVWA/Juice Shop/bWAPP/Mutillidae/WebGoat/sqli-labs/upload-labs/pikachu/DVGA/DVNA |
| 8090 | 导航门户 | nginx 静态页,所有靶场入口一页看全 |
| 3307 / 3308 | MySQL 5.7 / 8.0 | 弱口令与写文件链靶标 |
| 6380 | Redis 未授权 | 无密码靶标 |
| 2121~5901 | Metasploitable2 | 12 个非标端口(SSH 走 2222,避开宿主 22) |

二、编写 docker-compose.yml
全部靶场用一份 docker compose 统一编排,放在独立 bridge 网络 pentest-net(172.20.0.0/16),与宿主机网络隔离;重启策略统一为 unless-stopped,宿主机重启后靶场自动恢复。工作目录约定为 /data/pentest/lab。
mkdir -p /data/pentest/lab && cd /data/pentest/lab
docker network create pentest-net --subnet 172.20.0.0/16
下面是编排文件的核心内容(节选自实际交付版本,逐容器定义):
version: "3"
networks:
pentest-net:
external: true
services:
dvwa:
image: vulnerables/web-dvwa
container_name: dvwa
ports: "8081:80"
networks: pentest-net
restart: unless-stopped
juice-shop:
image: bkimminich/juice-shop
container_name: juice-shop
ports: "8082:3000"
networks: pentest-net
restart: unless-stopped
bwapp:
image: raesene/bwapp
container_name: bwapp
ports: "8083:80"
networks: pentest-net
restart: unless-stopped
mutillidae:
image: citizenstig/nowasp
container_name: mutillidae
ports: "8084:80"
networks: pentest-net
restart: unless-stopped
webgoat:
image: webgoat/webgoat
container_name: webgoat
ports: "8085:8080"
networks: pentest-net
restart: unless-stopped
sqli-labs:
image: acgpiano/sqli-labs
container_name: sqli-labs
ports: "8086:80"
networks: pentest-net
restart: unless-stopped
upload-labs:
image: c0ny1/upload-labs
container_name: upload-labs
ports: "8087:80"
networks: pentest-net
restart: unless-stopped
pikachu:
image: area39/pikachu
container_name: pikachu
ports: "8088:80"
networks: pentest-net
restart: unless-stopped
dvga:
image: dolevf/dvga
container_name: dvga
environment:
WEB_HOST: "0.0.0.0" # 关键:默认监听 127.0.0.1,必须放开(见坑 1)
WEB_PORT: "5013"
ports: "8089:5013"
networks: pentest-net
restart: unless-stopped
dvna:
image: appsecco/dvna:sqlite # 关键:latest 标签需外接 MySQL,必须用 sqlite 标签(见坑 3)
container_name: dvna
ports: "8091:9090"
networks: pentest-net
restart: unless-stopped
mysql57:
image: mysql:5.7.44
container_name: mysql57
environment:
MYSQL_ROOT_PASSWORD: "root"
ports: "3307:3306"
networks: pentest-net
restart: unless-stopped
mysql80:
image: mysql:8.0.46
container_name: mysql80
environment:
MYSQL_ROOT_PASSWORD: "root"
ports: "3308:3306"
networks: pentest-net
restart: unless-stopped
redis-unauth:
image: redis:alpine
container_name: redis-unauth
ports: "6380:6379"
networks: pentest-net
restart: unless-stopped
portal:
image: nginx:alpine
container_name: portal
volumes: "./portal/index.html:/usr/share/nginx/html/index.html:ro"
ports: "8090:80"
networks: pentest-net
restart: unless-stopped
Metasploitable2 比较特殊:镜像自带的启动配置只拉起 4 个服务,需要外挂启动脚本,单独放第 2 篇讲。先把其余 14 个容器跑起来。
docker compose up -d
docker compose ps # 确认容器状态
三、启动排障:四个最常见的坑
「容器显示 Up」不等于「靶场能用」。这一节是全篇含金量最高的部分------四个坑都来自真实部署,且表象都极具误导性。
坑 1:容器内 200,宿主机却连接被重置
现象:DVGA 容器 Up、端口映射正常,但宿主机 curl 8089 直接报 Recv failure:连接被对方重设;进容器内访问 5013 却是 200。
根因:应用默认只监听容器内的回环地址 127.0.0.1,而 docker-proxy 是从容器网卡侧转发进来的,命中不了回环监听,于是直接 RST。解决办法就是在 compose 里注入环境变量把监听地址放开(上文 dvga 段的 WEB_HOST: "0.0.0.0")。
经验:凡「容器内能访问、宿主访问被 reset」,第一反应查应用监听地址是否 127.0.0.1,优先找它的 host/bind 配置项,而不是怀疑 Docker 端口映射或防火墙。
坑 2:Mutillidae 提示 Database Offline
现象:8084 页面能打开,顶部提示数据库离线;进容器 show databases 只有系统库。
根因:这个镜像不在启动时自动建库,站点根目录留了一个手动初始化页面。请求一次即可:
curl -s http://127.0.0.1:8084/set-up-database.php
回显连续出现 Executed query CREATE DATABASE ... result 1 即成功
注意一个细节:库名实际是 mutillidae,不是镜像名里的 nowasp,共建成 13 张表。页面上的 database-offline.php 只是提示页,不要被误导去改配置。同类问题也出现在 DVWA(setup.php 点 Create/Reset Database)和 bWAPP(install.php 点 install)。
坑 3:DVNA 容器启动即退
现象:dvna 容器启动几秒后退出,日志停在等待数据库连接。
根因:appsecco/dvna 的 latest 标签需要外接 MySQL,没有连接信息就一直等到失败。解决:换用自带 SQLite 的标签 appsecco/dvna:sqlite(上文已体现)。经验:拉「多形态」镜像前先确认标签,latest 往往不是自包含版本。
坑 4:大镜像拉取中途 unexpected EOF
现象:拉取 1GB 级镜像偶发中断,报 short read / unexpected EOF。解决:循环重试即可,Docker 分层缓存支持续传,已下载的层不会浪费;用 docker image inspect 判断是否拉齐。
四、导航门户:一页看全所有靶场
靶场一多,端口就记不住。写一个静态页挂到 8090,把地址、端口、默认口令整理成一张面板。在 /data/pentest/lab/portal/index.html 中做一个简单的表格页即可,每行一个靶场:
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>靶场导航门户</title></head>
<body><h1>渗透靶场导航门户 | 10.10.10.240</h1>
<table border="1" cellpadding="6">
<tr><th>靶场</th><th>地址</th><th>默认凭据</th></tr>
<tr><td>DVWA</td><td><a href="http://10.10.10.240:8081">:8081</a></td><td>admin / password</td></tr>
<tr><td>Juice Shop</td><td><a href="http://10.10.10.240:8082">:8082</a></td><td>无需登录</td></tr>
<!-- ...其余靶场按此格式补全... -->
</table></body></html>
门户页同时承担「健康面板」的职责:哪天发现某个入口打不开,直接进入第六节的排障流程。
五、一键验收:用数据说话
验收纪律只有一条:必须协议级验证。Docker 的端口发布由 docker-proxy 监听,容器内服务没起来,宿主机 TCP 照样能建连(假通),所以 HTTP 服务必须拿到状态码,不能只看端口通不通。
Web 靶场批量探活:拿 HTTP 状态码
for p in 8081 8082 8083 8084 8085 8086 8087 8088 8089 8090 8091; do
printf "%s -> %s\n" "
(curl -s -o /dev/null -w "%{http_code}" --max-time 10 -L http://10.10.10.240:"$p"/)"
done
非 HTTP 服务用 TCP 连通性判断,不要拿 curl 去测 FTP
for p in 3307 3308 6380; do
timeout 4 bash -c "echo >/dev/tcp/127.0.0.1/$p" 2>/dev/null \
&& echo "端口
p 关闭"
done
预期结果:8081~8091 全部返回 200 或 302(DVWA/bWAPP/DVNA 未登录时 302 跳登录属正常;WebGoat 根路径 404 属正常,入口是 /WebGoat/)。逐容器验证数据库类靶标:
docker exec mysql57 mysql -uroot -proot -N -e "select version();" # 5.7.44
docker exec mysql80 mysql -uroot -proot -N -e "select version();" # 8.0.46
docker exec redis-unauth redis-cli ping # PONG
全部通过后,在虚拟化软件层对宿主机打一次快照------这是「玩脏了随时回滚」的保险,也是本系列的固定习惯。
六、日常运维命令速查
|-----------------|------------------------------------------------------------------|
| 场景 | 命令 |
| 查看全部容器状态 | docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" |
| 编排视角查看 | cd /data/pentest/lab && docker compose ps |
| 重启单个靶场 | docker compose restart dvwa |
| 看某靶场日志 | docker logs --tail 20 dvwa |
| 修改了 compose 后生效 | docker compose up -d --force-recreate 容器名 |
| 临时停用某靶场 | docker stop dvwa |
| 资源余量检查 | free -h && df -h / |
6.1 初始化速查(哪些靶场要「先点一次」)
靶场分两类:自带数据型(sqli-labs、upload-labs、Juice Shop、DVGA,打开就能打)与要初始化型(DVWA、bWAPP、Mutillidae、pikachu,必须先跑一次安装页)。初始化入口如下:
|------------|----------------------------------------------------------|
| 靶场 | 初始化方式 |
| DVWA | 访问 /setup.php,点 Create/Reset Database(重置数据也用它) |
| bWAPP | 访问 /install.php,点 install(报 database already exists 属正常) |
| Mutillidae | 访问 /set-up-database.php,点 Setup/Reset the DB |
| pikachu | 访问 /install.php,点「安装/初始化」 |
6.2 纪律提醒
- 宿主机 22/80 是系统自身端口,靶场端口规划必须避开。
- 内存有限:不使用的 Vulhub 环境随用随起、用完即停,不要与常驻靶场抢内存。
- 所有靶场仅限内网教学,绝不能映射到公网;玩脏了随时 docker restart 或重跑初始化,也可回滚快照。
宿主机就绪后,下一篇配置攻击机:给 Kali 固定 IP、装齐工具,并单独解决 Metasploitable2「端口通、服务不通」的补全问题。