AIGC标识 从零搭建本地 AI 安全测试平台(一):Ubuntu2604_Docker四大漏洞靶场部署实战

适用场景:在隔离、获得授权的实验环境中搭建 Juice Shop、DVWA、WebGoat、crAPI,供另一台 Ubuntu 主机上的 AI 安全测试平台使用。
实战环境:2026 年 11 月,Ubuntu 26.04.1、Docker Engine、SSH、iptables-nft。
记录说明:文中“实测”部分来自本次实际命令及输出;“参考部署”是供重新搭建时使用的示例,并非声称当时逐条执行。具体镜像 tag、Compose 文件内容、安装来源未完整保留,重新部署时应以项目官方文档和实际镜像版本为准。

一、项目背景与目标

本次实验使用两台 Ubuntu 主机:

  • A 主机:javen03,Ubuntu 26.04,后续计划部署 Ollama、Qwen3 8B、Python 自动化测试控制器。
  • B 主机:yj2-Ubuntu2,Ubuntu 26.04.1,IP 192.168.18.237,网卡 enp0s31f6,运行 Docker 漏洞靶场。
  • B 主机已有业务:Nginx 文件共享使用 TCP 443、8443;SSH 使用 22;远程桌面使用 3389。任何实验配置都不能破坏这些服务。
  • 代理:局域网 SOCKS5 192.168.10.3:7892 仅用于按需获取难以直接访问的国外资源,不是漏洞靶场的必要出口。

目标包括:四个靶场运行正常;A 主机能够访问靶场;靶场不主动访问不必要的互联网和其他内网;crAPI 内部数据库及微服务互通;不重启 Docker、不清空现有防火墙规则、不影响文件共享服务。

1.1 网络拓扑

A:javen03(AI 测试主机)
    |
    | SSH TCP/22 + 本地端口转发
    v
B:192.168.18.237(Ubuntu 26.04.1)
    |-- 127.0.0.1:3000  -> Juice Shop
    |-- 127.0.0.1:8081  -> DVWA
    |-- 127.0.0.1:8082  -> WebGoat
    |-- 127.0.0.1:8888  -> crAPI Web
    |
    |-- Nginx 443/8443(既有文件共享,保持不变)
    |-- SSH 22、RDP 3389(保持不变)
    |
    `-- Docker 独立网桥
          172.18.0.0/16 Juice Shop
          172.19.0.0/16 DVWA
          172.20.0.0/16 WebGoat
          172.21.0.0/16 crAPI 多容器

为什么不直接暴露漏洞靶场到局域网? 这些应用有意保留安全缺陷,绑定 127.0.0.1 后由 SSH 隧道提供受控访问,减少意外暴露。注意:这不是完整的隔离措施,仍须审计容器到宿主机、IPv6 和持久化防火墙策略。

二、准备工作与风险边界

2.1 先确认既有服务

在 B 主机执行:

hostnamectl
ip -br addr
sudo ss -lntp | grep -E ':(22|3389|443|8443)\b'
systemctl is-active docker
sudo docker version
sudo docker compose version

本次实测:Docker 为 active;Nginx 监听 0.0.0.0:443 和 0.0.0.0:8443;SSH 监听 22;GNOME 远程桌面监听 3389。

安全原则:不要执行 iptables -F、nft flush ruleset、iptables -P FORWARD ACCEPT、随意 ufw reload 或重启 Docker。尤其不要把靶场映射到 0.0.0.0。

2.2 安装 Docker(仅新机器参考)

本次 B 主机已经安装 Docker,下面不是本次安装历史,而是新机器的准备检查。安装时优先按照 Docker 官方 Ubuntu 指南配置 apt 仓库、安装 docker-ce、docker-ce-cli、containerd.io、docker-buildx-plugin、docker-compose-plugin。不要在已有业务主机上为完成教程而重新安装或重启 Docker。

sudo docker info
sudo docker compose version
sudo docker network ls

2.3 记录镜像版本与部署配置

为了让未来重建可复现,建议在当前 B 主机采集以下信息(只读):

sudo docker ps -a --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}'
sudo docker inspect lab-juice-shop --format '{{.Config.Image}}'
sudo docker inspect dvwa-dvwa-1 --format '{{.Config.Image}}'
sudo docker inspect lab-webgoat --format '{{.Config.Image}}'
sudo docker inspect crapi-web --format '{{.Config.Image}}'

重要:当前对话没有完整的原始 Compose YAML、镜像 digest 和所有环境变量,因此无法保证仅靠本文就能一字不差地重建原部署。特别是 crAPI 多服务配置需要保留官方 Compose 文件和对应版本。

三、四个靶场的部署设计

3.1 端口规划(实测)

应用 B 宿主机绑定 容器端口 备注
OWASP Juice Shop 127.0.0.1:3000 3000 单容器
DVWA 127.0.0.1:8081 80 另有数据库容器
WebGoat 127.0.0.1:8082 8080 另有 127.0.0.1:9090
crAPI Web 127.0.0.1:8888 80 另有 30080、18443、30443 等映射

crAPI 曾与已有业务的 8443 端口冲突,因此最终使用 18443、30443 等不冲突的端口。实际运行时还存在 5500(chatbot)与 8025(MailHog)等回环映射。

3.2 Juice Shop(参考单容器部署)

在全新、专用的实验机上,可参考以下形式。实际使用前固定经过验证的镜像版本,不建议生产环境依赖 latest。

sudo docker network create juice-shop_default
sudo docker run -d \
  --name lab-juice-shop \
  --network juice-shop_default \
  -p 127.0.0.1:3000:3000 \
  bkimminich/juice-shop

这是一份参考重建示例,不是原始执行记录。现有主机上不要重复运行,否则会与已存在的容器/网络冲突。实测容器为 lab-juice-shop,IP 172.18.0.2。

3.3 DVWA(参考部署方式)

DVWA 在本次环境中通过 Compose 运行,应用容器为 dvwa-dvwa-1,数据库为 dvwa-db-1,网络 dvwa_dvwa。应用容器 IP 172.19.0.3,数据库 IP 172.19.0.2。

重新部署时建议获取 DVWA 官方仓库的 Compose 文件,只修改对外发布端口,把 Web 服务端口映射为:

# 仅示意需要修改的 ports 字段,不能替代完整 Compose 文件
ports:
  - "127.0.0.1:8081:80"

然后在 Compose 项目目录执行:

sudo docker compose config
sudo docker compose up -d

现有部署无需重新执行。DVWA 入口返回 302 是正常的跳转现象,不能误判为服务失败。

3.4 WebGoat(参考单容器部署)

本次环境使用 lab-webgoat,网桥 webgoat_default,IP 172.20.0.2。宿主机 8082 映射到容器 8080,9090 映射到 9090。

可参考的发布端口形式:

# 仅展示端口规划,镜像、环境变量和健康检查需以实际版本为准
ports:
  - "127.0.0.1:8082:8080"
  - "127.0.0.1:9090:9090"

访问时应使用应用路径 /WebGoat,而不是仅检查 /。本次实测根路径返回 404,但 /WebGoat 经重定向后返回 200。

3.5 crAPI(多容器 Compose)

crAPI 是本次最复杂的靶场,包含 Web 前端、Identity、Community、Workshop、Chatbot、PostgreSQL、MongoDB、ChromaDB、MailHog 等组件。建议从官方 crAPI 项目获取与目标版本对应的完整 Compose 文件,不要根据零散端口表自行拼凑完整 YAML。

本次 docker_default 网桥中实测容器及地址:

容器 IP
postgresdb 172.21.0.2
api.mypremiumdealership.com 172.21.0.3
chromadb 172.21.0.4
mailhog 172.21.0.5
mongodb 172.21.0.6
crapi-identity 172.21.0.7
crapi-community 172.21.0.8
crapi-chatbot 172.21.0.9
crapi-workshop 172.21.0.10
crapi-web 172.21.0.11

部署时应特别检查是否占用了既有 Nginx 的 443、8443,并把需要测试的 Web 入口限制到 127.0.0.1。这些 IP 是本次实测值,不是持久的固定地址;容器重建后可能改变。

四、验证 B 主机本地四个靶场

sudo docker ps -a --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'

for item in 'JuiceShop:3000:/' 'DVWA:8081:/' 'WebGoat:8082:/WebGoat' 'crAPI:8888:/'; do
  IFS=: read -r name port path <<< "$item"
  printf '%-12s ' "$name"
  curl --noproxy '*' -sS --connect-timeout 3 --max-time 10 \
    -o /dev/null -w 'HTTP=%{http_code}\n' \
    "http://127.0.0.1:${port}${path}"
done

本次实际结果:

靶场 结果 说明
Juice Shop 200 HTTP 正常
DVWA 302 应用跳转
WebGoat /WebGoat 最终 200 入口正常
crAPI 200 Web 入口正常

HTTP 200/302 仅代表入口可达,不能替代注册登录、数据库写入、靶场练习等功能测试。

五、WebGoat unhealthy 的真实原因

这是本次排障中非常典型的一例:docker ps 显示 WebGoat Up (unhealthy),但浏览器和 curl 都可以访问。

排查命令:

sudo docker inspect lab-webgoat --format '{{json .State.Health}}'
curl --noproxy '*' -sS -L -o /dev/null \
  -w 'HTTP %{http_code}\n' --max-time 10 \
  http://127.0.0.1:8082/WebGoat

实际健康检查日志:

Status: unhealthy
FailingStreak: 7890
/bin/sh: 1: curl: not found

因此 unhealthy 的直接原因是健康检查命令依赖 curl,但容器里没有该程序;不能由此认定 WebGoat 应用故障。本次 /WebGoat 最终返回 HTTP 200。后续应根据镜像中实际可用的工具调整健康检查,或在构建镜像时补齐依赖;本文未执行重建,不将其写成已修复。

六、A 主机通过 SSH 隧道访问 B 靶场

所有靶场仅监听 B 的 127.0.0.1,A 主机直接访问 192.168.18.237:3000 不应成功。使用 SSH 本地端口转发,不必把有漏洞的服务暴露到局域网。

在 A 主机执行(SSH 账号根据 B 实际账号调整):

ssh -o ExitOnForwardFailure=yes -N \
  -L 13000:127.0.0.1:3000 \
  -L 18081:127.0.0.1:8081 \
  -L 18082:127.0.0.1:8082 \
  -L 18888:127.0.0.1:8888 \
  yj2@192.168.18.237

保持该终端运行,在 A 主机另开终端验证:

curl --noproxy '*' -sS -o /dev/null -w 'JuiceShop HTTP %{http_code}\n' http://127.0.0.1:13000/
curl --noproxy '*' -sS -o /dev/null -w 'DVWA HTTP %{http_code}\n' http://127.0.0.1:18081/
curl --noproxy '*' -sS -o /dev/null -w 'WebGoat HTTP %{http_code}\n' http://127.0.0.1:18082/WebGoat
curl --noproxy '*' -sS -o /dev/null -w 'crAPI HTTP %{http_code}\n' http://127.0.0.1:18888/

A 主机实测结果:Juice Shop 200、DVWA 302、WebGoat 302、crAPI 200,说明 SSH 隧道及四个入口均可达。SSH 会话退出后隧道断开;后续若需常驻,应另行设计密钥、访问控制与服务守护。

七、Docker 网络和防火墙故障排查

7.1 Docker 网桥与子网(实测)

sudo docker network ls
sudo docker network inspect docker_default
ip -br link show type bridge
sysctl net.ipv4.ip_forward
sudo iptables -L FORWARD -n -v --line-numbers
sudo iptables -L DOCKER-USER -n -v --line-numbers

实际网桥:

br-0c45b8201d1f  Juice Shop  172.18.0.0/16
br-bd58040c84aa  DVWA        172.19.0.0/16
br-3c90cf5d4e3b  WebGoat     172.20.0.0/16
br-81e33e196878  crAPI       172.21.0.0/16

本次 net.ipv4.ip_forward = 1;但 IPv4 FORWARD 默认策略是 DROP,Docker 的 DOCKER-USER 链显示 0 references,且没有常见的 Docker 转发跳转规则。不要因为 DOCKER-USER 链存在,就误以为写入该链的规则一定生效。

7.2 为什么最初 SOCKS5 测试不通?

最初从 Juice Shop 容器网络命名空间访问 192.168.10.3:7892 超时。抓包观察到 SYN 从容器进入 veth 和 Docker 网桥,但没有从物理网卡 enp0s31f6 发出;FORWARD 默认 DROP 且无放行规则。

当时为定位问题,临时添加了两条精确的转发规则:

sudo iptables -I FORWARD 1 \
  -i br-0c45b8201d1f -o enp0s31f6 \
  -s 172.18.0.2/32 -d 192.168.10.3/32 \
  -p tcp --dport 7892 -j ACCEPT

sudo iptables -I FORWARD 2 \
  -i enp0s31f6 -o br-0c45b8201d1f \
  -d 172.18.0.2/32 \
  -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

随后,使用宿主机 curl 进入容器网络命名空间测试:

PID=$(sudo docker inspect -f '{{.State.Pid}}' lab-juice-shop)
sudo nsenter -t "$PID" -n \
  curl --socks5-hostname 192.168.10.3:7892 \
  --connect-timeout 10 --max-time 20 \
  -I https://example.com

实测成功建立到代理的 TCP 连接,远端返回 HTTP/2 200。这证明转发链是先前故障的重要原因,不意味着漏洞靶场应该长期通过代理上网。

7.3 纠正设计:代理只用于按需下载资源

用户原本配置 SOCKS5,是因为部分国外网站或镜像资源无法直接访问。漏洞靶场作为被测目标,通常不需要主动访问互联网。更合理的设计是:宿主机或镜像下载按需使用代理;靶场容器的出站访问默认受限;A 主机通过 SSH 隧道进入靶场。

因此上述两条临时规则后来被精确删除,而不是清空整个防火墙:

sudo mkdir -p /root/pentest-lab-backup
sudo iptables-save > /root/pentest-lab-backup/before-isolation.rules

sudo iptables -D FORWARD \
  -i br-0c45b8201d1f -o enp0s31f6 \
  -s 172.18.0.2/32 -d 192.168.10.3/32 \
  -p tcp --dport 7892 -j ACCEPT

sudo iptables -D FORWARD \
  -i enp0s31f6 -o br-0c45b8201d1f \
  -d 172.18.0.2/32 \
  -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

sudo iptables -L FORWARD -n -v --line-numbers

本次实际执行后,FORWARD 显示:

Chain FORWARD (policy DROP ...)
num pkts bytes target prot opt in out source destination

即默认 DROP,无显式规则。此状态在当前环境下使测试的容器出站请求超时,但不能简单等同于完整隔离;也不能不经评估就把这种缺失 Docker 转发跳转的状态推广到其他 Docker 主机。

7.4 UFW 与 Docker 的关系:不能未经证实就归因

本次检查中 ufw 服务显示 active,iptables 使用 nf_tables 后端;Docker 的部分过滤链存在但未挂接。现有日志和规则不足以确定是谁删除或覆盖了 Docker 转发规则。不能断言 UFW 就是根因。后续若追查,应结合 UFW 配置、nftables 规则集、Docker 日志和重启时间线取证;但这不是完成靶场部署的前置条件。

八、验证靶场主动访问受限

使用 nsenter -n 调用宿主机 curl,进入目标容器网络命名空间,不依赖容器内是否安装 curl:

for c in lab-juice-shop dvwa-dvwa-1 lab-webgoat crapi-web; do
  echo "===== $c ====="
  PID=$(sudo docker inspect -f '{{.State.Pid}}' "$c")
  for target in http://1.1.1.1 http://192.168.18.1 http://192.168.10.3:7892; do
    echo "目标: $target"
    sudo nsenter -t "$PID" -n \
      curl --noproxy '*' -sS --connect-timeout 2 --max-time 4 \
      -o /dev/null -w 'HTTP=%{http_code} TCP=%{time_connect}s\n' \
      "$target" || echo '连接失败'
  done
done

本次实测:四个靶场对以上三个目标均返回 curl (28) Connection timed out、HTTP=000。说明这些指定地址与端口在测试时不可达。特别注意:SOCKS5 端口不是 HTTP 服务,HTTP 协议错误与 TCP 连接失败不是一回事;本次观察到的是连接超时。

不能过度解读:这些结果并未验证所有目的端口、容器到 B 宿主机的 INPUT 路径、IPv6、DNS、跨网桥路径和重启后的策略。隔离目前属于初步验证通过,未完成正式安全审计。

九、crAPI 内部通信:四次 FAIL 到四次 PASS

9.1 第一次测试为什么全部失败?

最初采用以下思路:

PID=$(sudo docker inspect -f '{{.State.Pid}}' crapi-identity)
sudo nsenter -t "$PID" -n timeout 3 bash -c 'echo > /dev/tcp/postgresdb/5432'

结果 crAPI 四项测试全部 FAIL。但各容器多数显示 healthy,数据库及应用端口也在监听。

原因是:nsenter -n 仅切换网络命名空间,不会自动切换 /etc/resolv.conf 等文件系统环境。宿主机 Bash 无法保证能解析 Docker 内部的 postgresdb、mongodb 等容器名称。这类 FAIL 不能直接认定 TCP 网络故障。

9.2 改用实测容器 IP 测试

check_tcp() {
  local source="$1" target_ip="$2" port="$3" pid
  pid=$(sudo docker inspect -f '{{.State.Pid}}' "$source")
  printf '%-22s -> %-16s : %-5s ' "$source" "$target_ip" "$port"
  if sudo nsenter -t "$pid" -n timeout 3 bash -c \
    "echo > /dev/tcp/$target_ip/$port" 2>/dev/null; then
    echo PASS
  else
    echo FAIL
  fi
}

check_tcp crapi-identity 172.21.0.2 5432
check_tcp crapi-community 172.21.0.6 27017
check_tcp crapi-web 172.21.0.7 8080
check_tcp crapi-web 172.21.0.8 8087

实测输出:

crapi-identity         -> 172.21.0.2       : 5432  PASS
crapi-community        -> 172.21.0.6       : 27017 PASS
crapi-web              -> 172.21.0.7       : 8080  PASS
crapi-web              -> 172.21.0.8       : 8087  PASS

这证明四条核心 TCP 路径畅通。它们位于同一 Docker 网桥中;本次 FORWARD 默认 DROP 并没有阻断这些实际测试的连接。但TCP 可连接不等于完整业务链路和数据库读写已验证。

十、最终验收结果与未完成事项

项目 本次结论 证据
Juice Shop 部署 已完成基础部署 HTTP 200;A 侧隧道 HTTP 200
DVWA 部署 已完成基础部署 HTTP 302;A 侧隧道 HTTP 302
WebGoat 部署 已完成基础部署 /WebGoat 最终 HTTP 200;A 侧 HTTP 302
crAPI 部署 已完成基础部署 Web HTTP 200;四条核心 TCP PASS
A → B 访问 已验证 SSH 隧道四个入口均可达
出站限制 初步验证 四个容器对三个指定目标均超时
原有服务 监听正常 22、3389、443、8443 均在监听
WebGoat 健康检查 待修正 curl: not found
全面网络隔离审计 未完成 宿主机 INPUT、IPv6、其他端口/路径未完整覆盖
防火墙规则持久化 未验证 未做重启后测试
crAPI 全功能业务测试 未完成 仅核心 TCP 和 Web 入口验证
A 主机 AI 测试平台 下一阶段 尚未部署 Ollama/Qwen3 8B 测试控制器

后续安全收尾建议

  1. 检查容器能否访问 B 宿主机的敏感端口和服务,必要时制定独立的容器到宿主机访问控制。
  2. 审计 IPv6 与容器 DNS 出口;检查 Docker 网桥及其他容器网络的实际策略。
  3. 评估 Docker 转发规则缺失的原因,明确重启 Docker/UFW/主机后的行为;实施持久化前准备回滚方案。
  4. 修正 WebGoat 的健康检查,但不要为了消除 unhealthy 标签而贸然重建正常运行的靶场。
  5. 进行 crAPI 注册、登录、API 调用和数据库实际读写验证;对其他三个靶场执行最小业务冒烟测试。
  6. 为 SSH 隧道增加密钥、权限和运行状态管理;不要把漏洞靶场直接映射到公网。

十一、下一阶段:AI 自动化安全测试平台

计划在 A 主机部署:

Ollama + Qwen3 8B(CPU 推理)
       |
       v
Python 测试编排程序
       |-- HTTP/API 基础检测
       |-- Nmap 授权服务识别
       |-- OWASP ZAP 被动扫描
       |-- 证据与日志归档
       v
本地 AI 分析、分类与 Markdown/HTML 报告
       |
       v
SSH 隧道 -> B 主机四个漏洞靶场

第一版建议采用白名单目标、只读/被动检测优先、主动测试人工审批的模式,不允许模型任意执行系统命令,也不把漏洞靶场扫描任务扩展到非授权网络。

十二、总结

这次实战的重点不只是“把四个 Docker 容器跑起来”,而是把漏洞靶场部署、最小暴露面、SSH 隧道访问、网络排障和验证证据串成完整流程。

最有价值的经验有四条:

  • 代理用于解决资源访问,不代表被测靶场需要代理上网。
  • HTTP 入口正常与 Docker 健康检查 healthy 是两个不同概念。 WebGoat 的 curl: not found 就是典型案例。
  • 进入网络命名空间不等于进入容器全部运行环境。 用宿主机 DNS 解析 Docker 容器名可能得到假阴性。
  • 默认 FORWARD DROP 不等于完整网络隔离。 既要验证容器间业务,也要考虑宿主机访问、IPv6、规则持久化与既有服务保护。

截至本次记录,四个靶场的基础部署和 A → B 访问已经完成,可以开始 AI 自动化测试平台建设;网络隔离的全面安全验收仍需后续补齐。


关键词:Ubuntu 26.04、Docker、Juice Shop、DVWA、WebGoat、crAPI、iptables、FORWARD、SSH Tunnel、网络隔离、OWASP、AI 安全测试

posted @ 2026-11-05 01:01  javen_xiao  阅读(3)  评论(0)    收藏  举报