刷到推文所以先尝试用一下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) sapperCLI:bash ~/tools/sapper-v2/install.sh- 模型网关:
~/.claude/settings.json的env(当前指向 DeepSeek 的 Anthropic 兼容端点), 军团还要DEEPSEEK_API_KEY(士兵层)——这些都在~/.sapper/env里,run-lab.sh会读
建镜像
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。
起军团
cd ~/tools/sapper-labbash 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 个窗格:
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:
cd ~/sapper-workbash engage.sh new <名字> --goal "评估 http://x" --scope "x:8080"bash engage.sh reason <名字> --dry-run # 只渲染提示词,0 tokenbash engage.sh auto <名字> --minutes 30bash engage.sh report <名字> # 导出 REPORT.md裸 sapper 的等价写法:
cd <目标目录>sapper setup --goal "..." --agents # 建 .sapper/ + 项目级 hookssapper dispatch reason # 提意图写回图sapper auto --skip-permissions # 推到 goalsapper view # 交互式 DAG 快照sapper teardown --purgesapper 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网络用容器名互访。
清理
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
# 在 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二、装
cd ~/sapper-labDEEPSEEK_API_KEY=sk-xxx bash install-kali-native.sh脚本按顺序做五件事:
apt逐个装工具链(tmux / nmap / sqlmap / ffuf / nuclei / impacket / seclists …), 已装的跳过,缺一个只警告不中断- 装 claude CLI(官方原生安装器
https://claude.ai/install.sh,不需要 node), 落到~/.local/bin/claude,并把该路径写进.zshrc/.bashrc - 布局:仓库软链到
/opt/sapper,建/work/runs并 chown,sapper软链进~/.local/bin,/sapper-setup技能软链进~/.claude/skills - 写网关:
~/.sapper/env(600)+~/.claude/settings.json的env块 - 冒烟:
sapper --version、claude -p实际打一次模型
为什么路径要软链:军团脚本里 /opt/sapper/docker/commander/*.sh、/opt/sapper/lib/arena.py、
/work/runs 都是硬编码,不做软链直接跑不起来。
三、验证
sapper --version # sapper 1.0.0claude --version # 2.xls -l /opt/sapper /work/runs # 软链和目录都在source ~/.sapper/env && claude -p "reply OK" --output-format json | head -c 200四、跑军团
cd ~/sapper-labbash run-lab-native.sh up # 假题板 :8080 + 靶机 a :8081 / b :8082bash run-lab-native.sh status另开一个终端起指挥官:
source ~/.sapper/envexport BENCHMARK_BASE_URL=http://127.0.0.1:8080 BENCHMARK_TOKEN=mock-tokenexport SAPPER_TOTAL_MINUTES=20 SAPPER_MINUTES_PER_CHALLENGE=6 SAPPER_MAX_WORKERS=4bash /opt/sapper/docker/commander/entrypoint.shentrypoint 会:起 tmux 会话 sapper-cmd → 拉起 claude TUI → 发 bootstrap 提示词 →
后台起 watchdog → 前台 tail drive.log。看到 [worker] xxx: TUI spawned 就是派单成功了。
收工:
bash run-lab-native.sh down # 停题板和靶机五、只跑单 agent(推荐日常用这个)
比赛那套题板不需要,一个目标一张图:
# 作业封装在 ~/sapper-work(Mac 上已建好,一起 scp 过去)cd ~/sapper-workbash engage.sh new <名字> --goal "评估 http://target 的登录面" --scope "target,10.0.0.5"bash engage.sh reason <名字> --dry-run # 只看它怎么想,0 tokenbash engage.sh auto <名字> --minutes 30 # 首次建议加 --no-skip,逐步确认bash engage.sh report <名字> # 导出 REPORT.mdbash engage.sh done <名字> --yes裸 sapper 等价写法:
cd <目标目录>sapper setup --goal "..." --agentssapper dispatch reasonsapper auto --skip-permissionssapper viewsapper 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看清每步
九、卸载
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 / downruns/—— 战役产物(每题的.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结合起来看看会不会提高漏洞的命中率。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!