刷到推文所以先尝试用一下sapper,用workbuddy制作了sapper-lab —— 即在本机跑 sapper-v2 军团

3126 字
16 分钟
刷到推文所以先尝试用一下sapper,用workbuddy制作了sapper-lab —— 即在本机跑 sapper-v2 军团

首先介绍一下这是个什么东西#

sapper 是一个「给 AI 渗透测试用的共享记忆 + 指挥循环」。它不负责让模型变聪明,负责让 AI 别失忆、别重复劳动、别把上下文撑爆。

它要解决的三个具体问题:

  • 失忆:单个 agent 干到第 20 步时,前面扫出来的端口、试过的口令、确认过的漏洞,全被挤在上下文里慢慢模糊。换个会话、上下文压缩、或者进程崩了,等于从零开始。
  • 浪费:多个 agent 并行时,A 扫过的端口 B 又扫一遍;同一款产品同一版本,换个 IP 又从指纹开始摸。
  • 记不住结论:分不清「我确认了」和「我猜的」,最后报告里写着「可能存在 SQL 注入」。

它的答案很朴素:把所有已确认的东西写进磁盘上的事实图,让每个 agent 每一步都从图里读起,干完再写回图。​

两个概念,其余都是围绕它们的语法糖:

  • **fact(事实)**​ — 一条已确认的观察。写入即不可变。带类型:host / service / vuln / cred / loot / flag / note。
  • **intent(意图)**​ — 一件待办的事。它从哪些 fact 出发(from),会产出哪个 fact(to)。to 还是空的时候,这条边就是「未完成」。

定位:它到底该在什么位置#

我之前想过「这平台只能打比赛吗,能不能像 ARTEX 那样挖洞」——答案是能,但它不是扫描器。

它不做发现。没有爬虫、没有 payload 库、没有指纹识别。第一步的「8080 开着、是 Tomcat」得你自己 nmap 出来再 sapper fact 写进去。

它做的是「不丢结论 + 不乱花 token + 可交付」。也就是 ARTEX 那类工具最弱的一环:扫完 300 个 endpoint,人已经不知道哪条线索验证过了。

所以合理分工是:nmap / nuclei / ffuf / 手工 burp 负责产出原始事实 → sapper 负责组织、判重、决定下一个动作、沉淀成报告。engage.sh 里 reason 那一步就是让模型只做「看图,说下一步」,不碰工具。

补一句边界,免得踩坑:auto 会真的执行命令(—dangerously-skip-permissions),在客户生产环境上跑之前务必先在本地靶场把 templates/ 换成 templates-vuln/(suspected/confirmed/exploited 三档 + 最小 proof + 强制证据落 doc),否则模型很容易把「可能存在」直接写成「确认存在」。


sapper-lab — macOS + Docker Desktop 跑完整军团镜像#

适合验证上游形态、复现比赛架构。代价:16GB 镜像、无真 host 网络、nmap -sS 受限。 起步:bash build-images.sh → bash run-lab.sh up

这套东西是给 ~/tools/sapper-v2(上游 N1nEmAn/sapper-v2,MIT)配的本地实验环境。

上游的军团形态(docker/commander/)是绑死在腾讯 TSecBench 比赛 API 上的:容器启动第一件事就校验 BENCHMARK_BASE_URL / BENCHMARK_TOKEN,指挥官靠 lib/arena.py 读”题板”派单。没有比赛平台就没有板子,容器直接 FATAL 退出。

所以这里补了两块上游没给的东西:

  • mock_bench.py —— 按 lib/arena.py 的契约实现的假题板(5 个端点,纯标准库)
  • target/ —— 两道故意的洞(目录穿越、命令注入),跑在独立容器里,flag 在容器内

从结果来看,sapper是纯为了打比赛而生的。

前置#

  • Docker Desktop(含 daemon 在跑)
  • 宿主机 tmux(看 TUI 用,brew install tmux)
  • sapper CLI:bash ~/tools/sapper-v2/install.sh
  • 模型网关:~/.claude/settings.json 的 env(当前指向 DeepSeek 的 Anthropic 兼容端点), 军团还要 DEEPSEEK_API_KEY(士兵层)——这些都在 ~/.sapper/env 里,run-lab.sh 会读

建镜像#

Terminal window
bash ~/tools/sapper-lab/build-images.sh
# 只想建前两层:bash build-images.sh tmux

三层结构:latest(Kali 工具链)→ tmux(加 tmux + TUI 驱动)→ commander(加 worker.sh / watchdog.sh / SKILL.md / codex)。最终 5.61GB。

构建踩的坑#

  • buildx 权限:本机 ~/.docker/buildx 有 operation not permitted,docker build 直接挂。脚本用 DOCKER_CONFIG=/tmp/sapper-ctx + DOCKER_HOST=unix://$HOME/.docker/run/docker.sock 走 legacy builder 绕开(隔离 config 后 docker 不再自动读 context,所以要显式给 socket)。

  • kali 升到 python3.14,pwntools 装不上:filebytes 在 3.14 上构建报 AttributeError: module 'ast' has no attribute 'Str',整条 pip 命令失败会中止构建。 docker/Dockerfile 和 docker/Dockerfile.commander 是打过补丁的副本(pwntools/ropper 降级成可选,失败只 WARN),上游仓库保持干净。

  • 代理:Dockerfile 里 apt 用清华 TUNA 源,且显式 env -u http_proxy 走直连——本机 全局代理会把国内镜像拖到 260KB/s。

起军团#

Terminal window
cd ~/tools/sapper-lab
bash run-lab.sh up # 靶机 a/b + 假题板(:8080) + 指挥官容器
bash run-lab.sh status # 题板进度 + 日志尾
docker logs -f sapper-cmd # 指挥官 / 看门狗实时日志
docker exec -it sapper-cmd tmux attach -t sapper-cmd # 钻进 TUI,Ctrl-b d 脱离
bash run-lab.sh down # 拆掉(--all 连靶机镜像和网络一起删)

预算变量在启动时透传,默认:总预算 40 分钟、每题 10 分钟、最多 4 个窗格:

Terminal window
SAPPER_TOTAL_MINUTES=20 SAPPER_MINUTES_PER_CHALLENGE=6 bash run-lab.sh up

架构上只有三个窗口:commander(读板 → 判断 → 派单,不亲手执行)、worker 窗格 (将军/士兵,各自一个 .sapper 图)、watchdog(纯脚本保活:崩了 claude --continue 拉起、key 轮换、到点收卷)。

实测结果#

两道题,web-lfi-01(目录穿越)和 web-cmdi-01(命令注入):

  • 题板:1/1 DONE、1/1 DONE
  • 事实图:f001 host → f002 service → f003 vuln → f004 loot → f005 flag(submitted)
  • 指挥官 1 分钟内派完两个 worker,完成后自己 kill pane、close 实例,并写了 runs/commander-state.md 交接文档
  • 产物留在 runs/(每题的 .sapper/、drive.log、pane-env/)

只跑单 agent(这条不用 docker)#

题板和容器都不需要,适合对一个目标做多步渗透记录。作业封装见 ~/sapper-work:

Terminal window
cd ~/sapper-work
bash engage.sh new <名字> --goal "评估 http://x" --scope "x:8080"
bash engage.sh reason <名字> --dry-run # 只渲染提示词,0 token
bash engage.sh auto <名字> --minutes 30
bash engage.sh report <名字> # 导出 REPORT.md

裸 sapper 的等价写法:

Terminal window
cd <目标目录>
sapper setup --goal "..." --agents # 建 .sapper/ + 项目级 hooks
sapper dispatch reason # 提意图写回图
sapper auto --skip-permissions # 推到 goal
sapper view # 交互式 DAG 快照
sapper teardown --purge

sapper dispatch / auto 调的是 claude -p,模型由 ~/.claude/settings.json 的 env 决定。

故障排查#

  • 容器秒退,日志第一行 FATAL: BENCHMARK_BASE_URL is not set:run-lab.sh 没传BENCHMARK_*,或你直接 docker run 了镜像。军团必须有一个题板。

  • cannot reach .../openapi/v1/challenges:题板没起来或不在同一网络。宿主机自测要curl --noproxy '*' http://127.0.0.1:8080/healthz(本机有全局代理,不加会把请求吞成 502)。

  • worker 窗格刚出现就消失:正常——指挥官判定该题 DONE 后会 kill pane 释放实例。

  • sapper arena challenges 崩 AttributeError: 'list' object has no attribute 'get': 上游 bug(lib/sapper.py:1833 对 list 调 .get())。军团走 worker.sh 里的 arena() shell 函数,不受影响;只是这个 CLI 子命令用不了。

  • 题板响应口径:bench/worker.sh 读顶层字段(container_addr / correct),sapper flag --submit 和 sapper arena 读 status==200 + result 包装。假题板把payload 同时平铺到顶层和 result,两边都满足。

  • --network host 别用:macOS 的 Docker Desktop 没有真 host 网络,所有容器进同一个sapper-lab 网络用容器名互访。

清理#

Terminal window
bash run-lab.sh down --all # 容器 + 靶机镜像 + 网络
docker rmi sapper-agent:latest sapper-agent:tmux sapper-agent:commander # 收 16GB

安全边界#

靶机是独立容器,命令注入只落在容器内 /var/flags/,碰不到宿主机;flag 是固定字符串。 这套环境只用于验证军团流程,别把 --skip-permissions 指向真实资产或未授权目标。


Kali 原生方案:把 sapper 装进 Kali VM(不用 Docker)#

为什么原生更好#

  • 网络:macOS 的 Docker 跑在嵌套 VM 里,没有真 host 网络。原生就是普通 Linux 网络, 题板直接 127.0.0.1:8080,打同机其他 VM 也不用绕。
  • 权限:nmap -sS、tcpdump、raw socket、impacket 在容器里要额外 cap 或只能降级, Kali 里 sudo 就完事。
  • 少四个坑:5.6GB 镜像、~/.docker/buildx 的权限问题、kali 的 py3.14 打补丁、/work/runs 挂载。
  • 上游本来就是容器化给比赛用的:Dockerfile 注释写明是为了交 TSecBench 镜像 (≤3GB、非 root、失败要 fail loud),不是因为比裸机好用。

代价:没有镜像的隔离性。所以靶机别放在这台 Kali 上裸跑(见最后一节)。

前置#

  • arm64 的 Kali VM(x86_64 在 Apple Silicon 上走模拟,慢到没意义)
  • 普通用户 + sudo(不要 root,原因见下)
  • 能访问 api.deepseek.com(国内直连即可)

一、把文件送进 VM#

Terminal window
# 在 Mac 上
scp -r ~/tools/sapper-v2 kali@<vm-ip>:~/tools/
scp -r ~/tools/sapper-lab kali@<vm-ip>:~/
scp -r ~/sapper-work kali@<vm-ip>:~/ # 单 agent 的作业封装,一起带过去
# VM 里没 git 的话先装:sudo apt install -y git
# 也可以直接在 VM 里 clone:
# git clone --depth 1 https://github.com/N1nEmAn/sapper-v2.git ~/tools/sapper-v2

二、装#

Terminal window
cd ~/sapper-lab
DEEPSEEK_API_KEY=sk-xxx bash install-kali-native.sh

脚本按顺序做五件事:

  1. apt 逐个装工具链(tmux / nmap / sqlmap / ffuf / nuclei / impacket / seclists …), 已装的跳过,缺一个只警告不中断
  2. 装 claude CLI(官方原生安装器 https://claude.ai/install.sh,不需要 node), 落到 ~/.local/bin/claude,并把该路径写进 .zshrc / .bashrc
  3. 布局:仓库软链到 /opt/sapper,建 /work/runs 并 chown,sapper 软链进 ~/.local/bin, /sapper-setup 技能软链进 ~/.claude/skills
  4. 写网关:~/.sapper/env(600)+ ~/.claude/settings.json 的 env 块
  5. 冒烟:sapper --version、claude -p 实际打一次模型

为什么路径要软链:军团脚本里 /opt/sapper/docker/commander/*.sh、/opt/sapper/lib/arena.py、 /work/runs 都是硬编码,不做软链直接跑不起来。

三、验证#

Terminal window
sapper --version # sapper 1.0.0
claude --version # 2.x
ls -l /opt/sapper /work/runs # 软链和目录都在
source ~/.sapper/env && claude -p "reply OK" --output-format json | head -c 200

四、跑军团#

Terminal window
cd ~/sapper-lab
bash run-lab-native.sh up # 假题板 :8080 + 靶机 a :8081 / b :8082
bash run-lab-native.sh status

另开一个终端起指挥官:

Terminal window
source ~/.sapper/env
export BENCHMARK_BASE_URL=http://127.0.0.1:8080 BENCHMARK_TOKEN=mock-token
export SAPPER_TOTAL_MINUTES=20 SAPPER_MINUTES_PER_CHALLENGE=6 SAPPER_MAX_WORKERS=4
bash /opt/sapper/docker/commander/entrypoint.sh

entrypoint 会:起 tmux 会话 sapper-cmd → 拉起 claude TUI → 发 bootstrap 提示词 → 后台起 watchdog → 前台 tail drive.log。看到 [worker] xxx: TUI spawned 就是派单成功了。

收工:

Terminal window
bash run-lab-native.sh down # 停题板和靶机

五、只跑单 agent(推荐日常用这个)#

比赛那套题板不需要,一个目标一张图:

Terminal window
# 作业封装在 ~/sapper-work(Mac 上已建好,一起 scp 过去)
cd ~/sapper-work
bash engage.sh new <名字> --goal "评估 http://target 的登录面" --scope "target,10.0.0.5"
bash engage.sh reason <名字> --dry-run # 只看它怎么想,0 token
bash engage.sh auto <名字> --minutes 30 # 首次建议加 --no-skip,逐步确认
bash engage.sh report <名字> # 导出 REPORT.md
bash engage.sh done <名字> --yes

裸 sapper 等价写法:

Terminal window
cd <目标目录>
sapper setup --goal "..." --agents
sapper dispatch reason
sapper auto --skip-permissions
sapper view
sapper teardown --purge

六、两条硬约束#

  • 不能 root:claude --dangerously-skip-permissions 在 root 下被直接拒绝,军团每个 worker 都会起不来。用 Kali 默认的 kali 用户(有 sudo)。
  • 别改路径:/opt/sapper、/work/runs 是脚本里写死的,改了要同步改 worker.sh / watchdog.sh / entrypoint.sh 里的十几处引用。

七、已知限制(脚本跑到哪一步验证过)#

  • 已在本机(同一套 Python 代码路径)实测:run-lab-native.sh 起板 + 两道德题可打; sapper bench 原生全流程 2 题 1 轮全解、题板 2/2 DONE。
  • 未在真实 Kali 里跑过:apt 包名在最新 kali-rolling 上的可用性、claude 安装器在 VM 网络下的成功率。这两步脚本都是”缺了只警告不中断”,装完把输出贴回来按报错补。

八、靶机隔离#

原生模式下靶机的命令注入是真的在本机执行,AI 拿到的 shell 就是你的 kali 用户的 shell。所以:

  • 只在一次性 VM / 快照里跑 run-lab-native.sh
  • 更稳的做法:Kali 只当攻击机,靶机留在 Docker 里(bash run-lab.sh up,但只起靶机和 题板,别起 commander 容器)
  • --skip-permissions 只对隔离环境用。对真实资产第一次跑,先 --no-skip 看清每步

九、卸载#

Terminal window
bash ~/tools/sapper-v2/install.sh --uninstall # 删 sapper 软链和 /sapper-setup 技能
sudo rm -f /opt/sapper # 删仓库软链(只是软链,不删代码)
sudo rm -rf /work # 删军团的战役目录
rm -f ~/.sapper/env # 删网关配置(含 key)

文件#

共用(两种方案都要):

  • mock_bench.py —— 假题板,按 lib/arena.py 的 5 个端点契约实现,纯标准库
  • target/app.py —— 靶机:/file?name= 目录穿越、/diag?host= 命令注入
  • target/Dockerfile —— 靶机镜像(Docker 方案用)

Docker 专属:

  • build-images.sh —— 三层镜像(latest → tmux → commander)
  • docker/Dockerfile、docker/Dockerfile.commander —— 打过 py3.14 补丁的副本
  • run-lab.sh —— up / status / down
  • runs/ —— 战役产物(每题的 .sapper/、drive.log)

Kali 专属:

  • install-kali-native.sh —— 工具链 + claude + /opt/sapper 布局 + 网关 + 冒烟
  • run-lab-native.sh —— 原生起题板(:8080) + 靶机(:8081/:8082)

安全边界#

靶机是故意留洞的服务,flag 是固定字符串,没有真实价值。Docker 方案里命令注入只落在 容器内,原生方案里它会真在你 VM 里执行——所以原生只在一次性 VM 里玩。 --skip-permissions 不要指向真实资产或未授权目标。


结论#

目前看下来,军团模式对于我来说需求是不大的,我现阶段的安排为挖洞而不是去比赛,所以后面的研究会往单agent去研究,将它与artex结合起来看看会不会提高漏洞的命中率。

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

Profile Image of the Author
LYH
LYH,安全方向的在校学生。方向集中在 Web 安全和密码学——Web 这边从 webshell 识别、文件包含到注入类题目都在练,密码学在啃 RSA 和格密码相关的内容,逆向和网络也有涉猎。日常工具链是 Mac + Docker。欢迎交流学习!
公告
欢迎来到我的博客!这里展示我的学习过程与结果。
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
站点统计
文章
52
分类
20
标签
32
总字数
311,598
运行时长
0 天
最后活动
0 天前

文章目录