第五章 – 重生之我是AI人:ReAct决策循环—LLM怎么”思考”

admin 2026-06-24 05:36:00 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文解析Hati渗透工具的ReAct决策循环。针对探索性特征,采用推理与行动交替设计六种动作。通过分层Prompt提升KV缓存命中率降成本,设八轮迭代上限。文章详述应对LLM异常输出的容错解析,及通过上下文压缩与多样性注入克服状态污染与模型偏置的实践,为AI安全开发提供直接参考。 综合评分: 91 文章分类: AI安全,渗透测试,安全工具,安全开发,WEB安全


cover_image

第五章 – 重生之我是AI人:ReAct 决策循环 — LLM 怎么”思考”

Khan安全团队

2026年6月22日 22:13 广东

在小说阅读器读本章

去阅读

以下文章来源于威胁情报Z分析 ,作者Gachong

威胁情报Z分析 .

国际网络安全威胁情报,地缘政治事件分析。

  1. 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 搞不定这种动态分支。

  1. 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 兜底

  1. 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 不要重复试已经试过的

  1. 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 元)很大程度靠这个。

  1. 8 轮迭代:为什么是 8?

这是经验值,不是理论最优。

我跑过 3 轮 / 8 轮 / 20 轮的对比实验:

为啥不是无限循环?

● 8 轮后边际收益骤降:多数目标的攻击面就那几个,8 轮基本遍历完

● LLM 上下文有限:超过 10 轮,早期信息开始被压缩/遗忘

● 成本:LLM 每次决策都烧钱,8 轮已经是性价比拐点

早停策略:LLM 输出 complete 立即结束(可能 3 轮就停)。8 是上限,不是必跑。

  1. 决策解析的容错

文件: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 也拒),关键词匹配虽然粗糙但能跑通,任务不中断。

  1. 一个真实的 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 输出:

{&nbsp;&nbsp;"action":&nbsp;"execute_skill",&nbsp;&nbsp;"skill_name":&nbsp;"xss-cross-site-scripting",&nbsp;&nbsp;"test_url":&nbsp;"http://testphp.vulnweb.com/search.php?search=<script>alert(1)</script>",&nbsp;&nbsp;"reasoning":&nbsp;"首页有 search 框,可能存在反射型 XSS,顺手测一下"}

执行结果:

请求: GET /search.php?search=<script>alert(1)</script>响应:&nbsp;<p>Results for&nbsp;<script>alert(1)</script>:</p>...特征匹配: 原样反射

判定:✅ 确认反射型 XSS,severity=medium,入栈。

第 3 轮

LLM 输入:

目标: testphp.vulnweb.com已发现:&nbsp;SQL&nbsp;注入(high), XSS(medium)已尝试:&nbsp;2&nbsp;个技能

LLM 输出:

{&nbsp;&nbsp;"action":&nbsp;"complete",&nbsp;&nbsp;"reasoning":&nbsp;"已发现 2 个漏洞,首页没有更多攻击面,任务完成"}

任务结束。只跑了 3 轮,不到 4 分钟。

  1. 反思:这套机制的局限

最后老实说,ReAct 不是银弹。已知问题:

  1. 上下文爆炸:超过 10 轮,早期信息被压缩(state/context_compressor.py 在处理这个)

  2. 状态污染:早期错误决策影响后续(LLM 看到错误信息可能误判)

  3. LLM 偏置:对热门漏洞类型有偏好(老提 SQL 注入,很少提 SSRF)

  4. 8 轮过早收敛:有些目标其实需要更长时间

我们的应对:

● state/context_compressor.py:对历史结果做摘要

● state/diversity_injector.py:强制多类型漏洞尝试

● 多模式确认:不依赖 LLM 自评,用特征字符串二次确认


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:Khan安全团队 《第五章 – 重生之我是AI人:ReAct 决策循环 — LLM 怎么”思考”》

评论:0   参与:  0