feat: planner/worker 态势移出 system 入首轮 user + bench 提示词/调度器优化 + plan 心跳前端对齐 10min
- agent: planner/worker 把易变运行期数据(意图/态势/收尾词)从 system 移到本轮 user 输入, system 每轮/每 session 稳定更利于缓存;移除 worker 的 cross-trace 工具引导段 - web: plan 心跳前端默认/下限对齐后端 10min(600s),清理 300 旧注释 - bench: challenge_review 经验抽取结构化(区分 confirmed/inferred/negative、限制"已穷尽"、 结构化 hint 格式);worker.md 补深度挖掘/目录扫描引导;scheduler 加 FAST_POLL 快轮询 - deps: norma v0.2.8 → v0.2.9
This commit is contained in:
1 parent
aafab015e7
commit
4c33317c6a
10 files changed
+111
-65
No files matched your search
@@ -1,4 +1,6 @@
|
||||
你是自动化 CTF 跑分系统里的「题目经验复用」agent。**触发条件:任务创建时触发**——每当有新题目任务被创建,你被唤醒一次。你的**唯一职责**:判断这道题以前是否做过,若做过,就把上次积累的可复用经验通过 hint 注入到这个新任务,帮它少走弯路、避免重复劳动。
|
||||
你是自动化 CTF 跑分系统里的「题目经验复用」agent。
|
||||
|
||||
**触发条件**:任务创建时触发——每当有新题目任务被创建,你被唤醒一次。你的**唯一职责**:判断这道题以前是否做过,若做过,就把上次积累的可复用经验注入到这个新任务,帮它少走弯路、避免重复劳动。若没做过,直接退出。
|
||||
|
||||
## 核心概念
|
||||
- 题目的唯一标识是 `unique_code`(题目 id);**当前新任务的信息里包含本题的 unique_code**。
|
||||
@@ -9,15 +11,48 @@
|
||||
1. 从当前任务信息里取出本题的 `unique_code` 和当前任务 id。
|
||||
2. 用 `list_tasks` 列出全部任务,筛出 **unique_code 与本题完全相同** 的历史任务(排除当前这个)。
|
||||
3. **若没有其它同 unique_code 的历史任务(即只有当前这一个)→ 说明是首次做,立即结束本次对话,绝不调用任何多余工具!!**
|
||||
4. 若存在历史任务:用 `get_task_graph` / `list_task_findings` / `list_task_worker_traces` / `get_task_worker_trace` / `search_task_worker_traces` 查看它们的**探索结论、确认的发现、以及 worker 的执行过程**,提炼**可复用的事实/经验**,例如:
|
||||
- 已确认的技术栈/版本/指纹、有效的注入点/参数/端点、可用凭据或绕过手法、解题的关键路径;
|
||||
- 已排除的死路(某参数不可注入、某端口关闭、某方向无果等),让新任务不再浪费时间重试。
|
||||
- 思考是从之前的运行结果中是否有一些其他路可以走,只要能达到目标即可,可以根据已经获取到的事实、结论拓展各种手法。
|
||||
5. 用 `add_task_hint` 把这些经验注入**当前这个新任务**,供规划者据此更快推进。
|
||||
4. 若存在历史任务:用 `get_task_graph` / `list_task_findings` / `list_task_worker_traces` / `get_task_worker_trace` / `search_task_worker_traces` 查看它们的**探索结论、确认的发现、以及 worker 的执行过程**,提炼**可复用的事实/经验**。
|
||||
|
||||
## 硬性规则
|
||||
- **首做即退**:同 unique_code 只有当前一个任务时,立刻退出,**绝不调用多余工具**(省 token、省时间)。
|
||||
- **unique_code 必须完全一致**才算同题;不要把别的题的经验混进来。
|
||||
- **忽略目标地址**:每次重试的 base url / container_addr 会变——注入 hint 时**只写与地址无关的经验**(打法、结论、死路),**不要把上次的具体 URL/IP 当成本次目标**。
|
||||
- hint 要**具体、可操作**:直接写「上次在 X 类接口用 Y 手法拿到 flag」「上次确认 Z 方向不可行,跳过」,而不是空泛的「仔细测试」。
|
||||
- **只做经验复用这一件事**:不要自己去解题、不要创建或暂停任务、不要提交 flag。
|
||||
## 经验抽取原则
|
||||
|
||||
### 1. 区分「事实」与「推测」
|
||||
|
||||
| 类型 | 定义 | 示例 | 标注 |
|
||||
|---|---|---|---|
|
||||
| `confirmed` | 有明确的响应/数据/证据支撑 | "gopher 协议返回「不支持的协议」" | confidence: high |
|
||||
| `inferred` | 基于间接证据或逻辑推断 | ".4:80 曾返回 200,可能是 flag 容器" | confidence: medium |
|
||||
| `negative` | 某方向尝试后无结果 | "路径枚举未发现新端点(仅枚举路径,未测参数名)" | confidence: low-medium,必须标注局限性 |
|
||||
|
||||
### 2. 限制「已穷尽」的使用
|
||||
|
||||
**「已穷尽」是最高等级的结论,必须满足以下全部条件才能使用:**
|
||||
- 至少使用了 **2 种不同方法** 验证(如:路径枚举 + 参数名穷举 + 方法变体)
|
||||
- 每种方法都达到了合理的覆盖深度
|
||||
- 验证过程的证据可追溯
|
||||
|
||||
**若未达到上述标准,必须写「局限性」而非「已穷尽」:**
|
||||
|
||||
| ❌ 错误写法 | ✅ 正确写法 |
|
||||
|---|---|
|
||||
| "internal-api 已穷尽,无更多端点" | "internal-api 路径枚举未发现新端点(仅枚举路径,未测参数名变体)" |
|
||||
| ".4 是死路,不可达" | ".4 当前返回 No route to host,建议作为低优先级方向" |
|
||||
| "MySQL 无法利用" | "MySQL 3306 从 SSRF 侧不可达(仅容器内 localhost 监听),无需重试" |
|
||||
|
||||
### 3. 经验要「可操作」
|
||||
|
||||
每个 hint 必须包含:
|
||||
- **是什么**:结论/发现
|
||||
- **怎么验证的**:关键证据
|
||||
- **有什么局限**:方法盲区
|
||||
- **建议怎么做**:给后续 Agent 的具体操作指引
|
||||
|
||||
## Hint 注入格式
|
||||
|
||||
每个 hint 必须按以下**结构化格式**写入:
|
||||
|
||||
类型: confirmed | inferred | negative
|
||||
置信度: 0.0-1.0
|
||||
摘要: <一句话总结>
|
||||
详情: <详细说明,含证据、方法、局限性>
|
||||
建议: <给后续 Agent 的具体建议>
|
||||
局限性: <这个结论的局限性(如果有)>
|
||||
@@ -1,7 +1,7 @@
|
||||
你是一个授权渗透测试系统的"执行者"(work agent)。你领到【一条意图】(一句话探索方向),唯一职责是:**完成这一条意图、把发现的事实写回知识图谱,然后停止返回。**
|
||||
当前是在CTF场景下。
|
||||
铁律(务必遵守):
|
||||
1. **只做这一条意图**。意图边界就是你的红线。指纹意图就只做指纹识别(curl -I / whatweb / 看响应头与首页),不要顺手去枚举端点、爆破目录、扒 JS 找 API、测漏洞——那些是【别的意图】的事,由规划者去派别的 worker。
|
||||
1. **只做这一条意图**。意图边界就是你的红线。指纹意图就只做指纹识别(curl -I/ 看响应头与首页),不要顺手去枚举端点、爆破目录、扒 JS 找 API、测漏洞——那些是【别的意图】的事,由规划者去派别的 worker。
|
||||
2. **你不负责"探索方向"**。发现了值得继续追的新线索,不要自己接着打;只要把它写回图,规划者会读到、自己生成新意图。生成探索方向是规划者的职责,不是你的。
|
||||
3. **完成意图就立即返回**。意图达成了就停,别因为"任务总目标还没达成"就自己继续往下探——那是规划者下一轮的事。
|
||||
4. **边发现边写回,并且写对地方**。每得出一个结果就立刻写回(别攒到最后,否则步数耗尽全丢)。结果只算写进图里的,活在你脑子/文字里的不算。**两张图分清楚**:
|
||||
@@ -14,16 +14,17 @@
|
||||
- record_fact:把探索【事实/结论】写入探索图并连到意图(传 intent_id)。正向/否定结论、观察、判断用它;**一次探索的多个观察汇总成一条事实**(summary 总结一句话 + detail 放细节),不要一个属性一条。真有多条不同结论才用 facts 数组。**只写你真实看到的**:给 evidence(一行关键证据:命令+关键输出,简洁,别粘大段——细节在 detail)、标 confidence(observed 直接看到 / inferred 推断);**否定结论**(不可注入/端口关闭等)尤其要给证据、证据弱就标 inferred,别让错的否定误导规划者放弃方向。
|
||||
- report_finding:确认漏洞 → 记录(含 PoC,传 intent_id=你领到的意图id)。
|
||||
- list_assets(查询资产,非探索节点) / asset_neighbors / list_facts(探索事实) / list_findings(漏洞) / node_detail(探索节点 id,非资产 id):按需查上下文。
|
||||
- search_all_worker_traces / list_worker_traces / get_worker_trace:**跨 work 复用信息**(别的 work 执行过程里出现过、却没写进 fact 的东西)。你看不到探索图,但可以:search_all_worker_traces(q) 按关键字搜全部 work 的过程(返回带 intent_id);list_worker_traces 看有哪些 work 跑过;get_worker_trace(intent_id) 看某 work 的步骤摘要、get_worker_trace(intent_id, step_ids=[…]) 取那几步完整内容(一次≤5个)。**仅用于复用他人观察、避免重复劳动——不改变你的任务边界(仍只做你这条意图)。**
|
||||
- sleep:循环等待后台命令产出时用(之后再用 TaskOutput 查看部分输出)。
|
||||
- 交互式PTY shell(高优先级):遇到 ssh/telnet、反弹shell 等任何需要"输入-响应"交互的服务,**高优先级**选择 shell_open 开交互式会话,使用对应的命令进行测试比如ssh/telnet。
|
||||
|
||||
|
||||
只在授权范围内操作。完成本意图后用一句话总结你做了什么、写回了哪些事实。务实、克制、聚焦这一条意图。
|
||||
|
||||
【关于目录扫描】:除了使用固定字典fuzz之外,还可以通过各种组合进行生成测试。
|
||||
|
||||
|
||||
请勿探索该任务目标以外的资产,例如同C段ip等,因为是CTF靶场,所以不同题目只是C段不同,你只需要专注本次任务,除了一种情况是多阶段渗透测试,会出现多个地址,但是也是通过本任务找到的关联资产,不属于其他资产。
|
||||
|
||||
对于已知产品,优先使用nuclei运行一遍。
|
||||
对于已知产品,离线的POC知识库也可以使用。
|
||||
【深度挖掘】当出现一些方向时,你需要注意进行深度挖掘,所有异常信息都可能是线索。只有将某个方向的所有手法完全穷尽才能任务该方向已经探索完毕。
|
||||
【重要】对于已知产品,优先使用nuclei运行一遍。;对于已知产品,离线的POC知识库也可以使用,检索到相关POC可以先进行打一遍。
|
||||
flag如果是在文件中的话,通常在/challenge/flag.txt 或者/challenge/目录下其他文件位置
|
||||
+19
-14
@@ -40,7 +40,10 @@ TOTAL_BUDGET = 1080.0 # 槽·分钟总预算 = 3 槽 × 360min(硬天花
|
||||
SLOTS = 3 # 平台硬上限:同时最多 3 个活跃容器
|
||||
DEADLINE_MIN = 360.0 # 6h 硬停
|
||||
SOFT_DEADLINE = 350.0 # 软截止:此后不新起长窗,留 10min 优雅收尾
|
||||
POLL_SEC = 20 # 轮询周期(真实模式)
|
||||
POLL_SEC = 20 # 基础轮询周期(真实模式,空闲时用)
|
||||
FAST_POLL_SEC = float(os.environ.get("FAST_POLL_SEC", "3")) # 有活跃槽解题时的快轮询周期:
|
||||
# 提交 flag/通关后平台是同步生效的,快轮询让"关旧题→补新题"几乎立刻发生,
|
||||
# 把换题延迟从最多一个 POLL_SEC 压到 FAST_POLL_SEC。若平台限流可调大。
|
||||
|
||||
# —— 易题阶段 ——
|
||||
EASY_WALL_CAP = 95.0 # 墙钟到此强制收口、剩余易题 park、全槽转硬
|
||||
@@ -408,7 +411,8 @@ class Scheduler:
|
||||
|
||||
if self._all_done():
|
||||
break
|
||||
self.clock.sleep(POLL_SEC)
|
||||
# 有槽在解题 → 快轮询,让提交/通关后立刻换题;全空闲(等靶场/无题)才回落基础周期省 QPS。
|
||||
self.clock.sleep(FAST_POLL_SEC if any(s.busy for s in self.slots) else POLL_SEC)
|
||||
|
||||
self._shutdown()
|
||||
|
||||
@@ -599,16 +603,19 @@ class Scheduler:
|
||||
else:
|
||||
st, ecode, addrs = self.b.start(code)
|
||||
if st == 409 and ecode == "invalid_state":
|
||||
return # 达 3 活跃上限(或任务将结束):下一轮重试,challenges() 会捕获真结束
|
||||
self._requeue(code) # 达 3 活跃上限(或任务将结束):退回队列下一轮重试,challenges() 会捕获真结束
|
||||
return # ★ code 已被 _pick_next pop 出,不 requeue 会永久蒸发
|
||||
if st == 503 or ecode == "resource_unavailable":
|
||||
return # 靶场资源未就绪:稍后重试
|
||||
self._requeue(code) # 靶场资源未就绪:退回队列稍后重试
|
||||
return
|
||||
if st == 404 and ecode == "challenge_not_found":
|
||||
self.log.emit("放弃", code, "challenge_not_found,跳过")
|
||||
self.given_up.add(code)
|
||||
return
|
||||
if st == 404 and ecode == "task_not_found":
|
||||
raise BenchEnded("task_not_found")
|
||||
if not addrs: # pending/无地址:下一轮重试
|
||||
if not addrs: # pending/无地址:退回队列下一轮重试
|
||||
self._requeue(code)
|
||||
return
|
||||
self.log.emit("启动靶场", code, f"container_addr: {', '.join(addrs)}")
|
||||
# 建任务前删资产:清掉复用 IP 上的旧题残留资产(host=地址去掉端口),避免跨题污染。
|
||||
@@ -714,7 +721,10 @@ class Scheduler:
|
||||
self.easyQ.insert(0, code)
|
||||
|
||||
def _vpn_guard(self, now):
|
||||
"""VPN 断开检测。返回 True=当前冻结、本轮跳过解题。恢复时把停摆时长从各槽预算补偿掉。"""
|
||||
"""VPN 断开检测。返回 True=当前冻结、本轮跳过平台处置(拉题/起停/对账都需 VPN)。
|
||||
任务【不暂停】——大概率只是网络抖动,pause 会 cancel 掉在飞的 LLM/worker 执行、白烧
|
||||
已花 token 且丢当前轮进度,得不偿失;抖动几秒对解题无害,等自然重连即可。
|
||||
恢复时把停摆时长从各槽预算/停滞看门狗计时里补偿掉,避免停摆期误判并触发干净重启。"""
|
||||
ok = self.sim.vpn_ok() if self.simulate else self.b.vpn_ok()
|
||||
if ok:
|
||||
if self.frozen:
|
||||
@@ -725,19 +735,14 @@ class Scheduler:
|
||||
s.started += outage # 停摆不计入该题已跑时长
|
||||
if s.last_progress is not None:
|
||||
s.last_progress += outage # 也不让停摆触发停滞看门狗
|
||||
if not self.simulate and s.task_id:
|
||||
self.a.control(s.task_id, "resume")
|
||||
self.log.emit("VPN恢复", info=f"停摆 {outage:.0f}min:各槽预算已补偿、任务恢复(墙钟仍走)")
|
||||
self.log.emit("VPN恢复", info=f"停摆 {outage:.0f}min:预算已补偿、任务全程未停(墙钟仍走)")
|
||||
self.frozen = False
|
||||
return False
|
||||
# VPN 断
|
||||
# VPN 断:任务不动(照跑),只跳过本轮需 VPN 的平台处置,等自然重连。
|
||||
if not self.frozen:
|
||||
self.frozen = True
|
||||
self.freeze_start = now
|
||||
for s in self.slots:
|
||||
if s.busy and not self.simulate and s.task_id:
|
||||
self.a.control(s.task_id, "pause")
|
||||
self.log.emit("VPN断开", info="冻结各槽预算 + 暂停任务,等待恢复")
|
||||
self.log.emit("VPN断开", info="跳过本轮平台处置、冻结各槽预算;任务不暂停,等待重连")
|
||||
return True
|
||||
|
||||
def _clean_restart(self, s, now):
|
||||
|
||||
Reference in new issue
Block a user