文章总结: 本文解析Hati渗透工具的ReAct决策循环。针对探索性特征,采用推理与行动交替设计六种动作。通过分层Prompt提升KV缓存命中率降成本,设八轮迭代上限。文章详述应对LLM异常输出的容错解析,及通过上下文压缩与多样性注入克服状态污染与模型偏置的实践,为AI安全开发提供直接参考。 综合评分: 91 文章分类: AI安全,渗透测试,安全工具,安全开发,WEB安全
第五章 – 重生之我是AI人:ReAct 决策循环 — LLM 怎么”思考”
Khan安全团队
2026年6月22日 22:13 广东
在小说阅读器读本章
去阅读
以下文章来源于威胁情报Z分析 ,作者Gachong
威胁情报Z分析 .
国际网络安全威胁情报,地缘政治事件分析。
- ReAct 是什么
ReAct = Reasoning + Acting。2022 年 Yao 等人提的论文。核心思想:
LLM 不应该一次性给答案,而应该:1. 想(Reason):分析当前情况2. 做(Act):执行一个动作3. 看(Observe):观察动作的结果4. 再想:基于新情况继续5. ...循环到收敛
跟普通 Chain-of-Thought 区别:
● CoT:一次性推理
● ReAct:循环推理 + 动作
为啥渗透测试场景特别需要 ReAct?因为这是一个探索性任务。你不知道前面会遇到什么 — 试 SQL 注入没成功,可能需要换 XSS;XSS 也没成功,可能要看认证绕过。每一步的选择依赖上一步的结果。CoT 搞不定这种动态分支。
- Hati 的 4 选 1 决策
LLM 每次循环,要从 4 个动作里选 1 个(其实更多,有 6 个,我下面展开):
class ActionSchema: execute_tool: str # 调 MCP 工具 execute_skill: str # 跑攻击技能 query_rag: str # 查 RAG generate_poc: str # LLM 自己生成 POC auth_bypass: str # 认证绕过测试 complete: str # 任务完成
为啥是这 4-6 个,不是更多也不是更少?
● 少了不行:如果只有 execute_tool,没法利用技能库;如果只有 execute_skill,没法处理技能没覆盖的情况
● 多了不行:LLM 选 7 个动作,准确率掉到 60%,工程上不划算
● 6 个是经验值:再多就用 query_rag + generate_poc 兜底
- ActionSchema 的设计
看 agents/orchestrator.py:82-200 的 think():
ACTION_SCHEMA = """你是一个渗透测试调度员。当前状态:- 目标: {target}- 已收集信息: {page_info}- 已尝试: {history}
可选动作:1. execute_tool - 调用 MCP 工具(适合:通用扫描、nmap、nuclei)2. execute_skill - 执行攻击技能(适合:已知类型的漏洞)3. query_rag - 查 RAG 知识库(适合:想参考历史 POC)4. generate_poc - 自己生成 POC(适合:现有知识都不匹配)5. complete - 任务完成
请输出严格 JSON:{{ "action": "...", "reasoning": "...", ...}}"""
有意思的设计点:
● JSON 严格输出:用大括号包起来,让 LLM 知道要结构化输出
● reasoning 字段必填:强制 LLM 说理由,逼它想清楚再动手 — 这就是 ReAct 的”Reasoning”
● history 字段:把前几轮的执行结果喂回去,让 LLM 不要重复试已经试过的
- Prompt 怎么写:分层设计
文件:config/prompts_layered.py(422 行)
这是另一个值得展开的话题 — 分层 Prompt。
传统做法:
[巨大的 system prompt][用户 prompt][对话历史][当前问题]
问题:每次只改一点点,但整个 prompt 都失效,KV-cache 命中率掉到 30%。
Hati 的做法:
[系统层:固定不变,KV-cache 命中] - system_prompt[任务层:本任务不变] - task_prompt[动态层:每轮变化] - dynamic_prompt[结果层:上一轮结果] - last_result
为啥这样?因为 LLM 服务端通常做 KV-cache。prompt 前缀不变就命中缓存,省 token 费 + 加速。Hati 跑得便宜(单任务 0.5-1 元)很大程度靠这个。
- 8 轮迭代:为什么是 8?
这是经验值,不是理论最优。
我跑过 3 轮 / 8 轮 / 20 轮的对比实验:
为啥不是无限循环?
● 8 轮后边际收益骤降:多数目标的攻击面就那几个,8 轮基本遍历完
● LLM 上下文有限:超过 10 轮,早期信息开始被压缩/遗忘
● 成本:LLM 每次决策都烧钱,8 轮已经是性价比拐点
早停策略:LLM 输出 complete 立即结束(可能 3 轮就停)。8 是上限,不是必跑。
- 决策解析的容错
文件:agents/orchestrator.py:312-331
LLM 不是 100% 听话的。偶尔会输出:
● “我建议用 sqlmap 测一下 SQL 注入”(自然语言)
● “json\n{…}\n”(代码块包裹)
● “对不起,我无法…”(拒答)
Hati 的容错策略:
def _parse_decision(self, response): # 1. 优先用正则抓 JSON json_match = re.search(r'\{.*\}', response, re.DOTALL) if json_match: try: return json.loads(json_match.group()) except json.JSONDecodeError: pass
# 2. JSON 失败 → 关键词匹配 response_lower = response.lower() if "auth_bypass" in response_lower: return {"action": "auth_bypass"} elif "skill" in response_lower: return {"action": "skill_based_attack"} elif "rag" in response_lower or "poc" in response_lower: return {"action": "rag_poc_attack"} else: return {"action": "complete"} # 兜底
为啥不直接 retry?因为 LLM 拒答可能是策略原因(retry 也拒),关键词匹配虽然粗糙但能跑通,任务不中断。
- 一个真实的 ReAct 轨迹(完整复现)
目标:http://testphp.vulnweb.com
第 1 轮
LLM 输入:
目标: testphp.vulnweb.com技术栈: PHP, Apache, MySQL端口: 80, 3306已尝试: 无
LLM 输出:
{ "action": "execute_skill", "skill_name": "sqli-sql-injection", "target_param": "artist", "test_url": "http://testphp.vulnweb.com/artists.php?artist=1", "reasoning": "3306 端口开放 + PHP+MySQL,SQL 注入概率高,优先测"}
执行结果:
请求: GET /artists.php?artist=1' OR '1'='1响应: You have an error in your SQL syntax near '' OR '1'='1'状态码: 200特征匹配: ["SQL syntax", "MySQL"]
判定:✅ 确认 SQL 注入,severity=high,入栈 state[“vulnerabilities”]。
第 2 轮
LLM 输入:
目标: testphp.vulnweb.com技术栈: PHP, Apache, MySQL已发现: 1 个 SQL 注入 (high)已尝试: sqli 技能
LLM 输出:
{ "action": "execute_skill", "skill_name": "xss-cross-site-scripting", "test_url": "http://testphp.vulnweb.com/search.php?search=<script>alert(1)</script>", "reasoning": "首页有 search 框,可能存在反射型 XSS,顺手测一下"}
执行结果:
请求: GET /search.php?search=<script>alert(1)</script>响应: <p>Results for <script>alert(1)</script>:</p>...特征匹配: 原样反射
判定:✅ 确认反射型 XSS,severity=medium,入栈。
第 3 轮
LLM 输入:
目标: testphp.vulnweb.com已发现: SQL 注入(high), XSS(medium)已尝试: 2 个技能
LLM 输出:
{ "action": "complete", "reasoning": "已发现 2 个漏洞,首页没有更多攻击面,任务完成"}
任务结束。只跑了 3 轮,不到 4 分钟。
- 反思:这套机制的局限
最后老实说,ReAct 不是银弹。已知问题:
-
上下文爆炸:超过 10 轮,早期信息被压缩(state/context_compressor.py 在处理这个)
-
状态污染:早期错误决策影响后续(LLM 看到错误信息可能误判)
-
LLM 偏置:对热门漏洞类型有偏好(老提 SQL 注入,很少提 SSRF)
-
8 轮过早收敛:有些目标其实需要更长时间
我们的应对:
● state/context_compressor.py:对历史结果做摘要
● state/diversity_injector.py:强制多类型漏洞尝试
● 多模式确认:不依赖 LLM 自评,用特征字符串二次确认
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:Khan安全团队 《第五章 – 重生之我是AI人:ReAct 决策循环 — LLM 怎么”思考”》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论