博客渲染 mermaid 图有点问题并且我懒得修,建议阅读github版本。
序
三周前参加了腾讯云举办的智能化渗透挑战赛(排名 18/613,前 10 名晋级线下决赛),核心代码已开源到GitHub LLM-Native Offensive Runtime 。受笔者知识所限,本文及开源内容可能存在不足和错误,欢迎各位读者批评指正。
比赛规则简介(DeepSeek总结)
本次大赛旨在推动 AI 大模型与网络安全技术的深度融合,探索智能体在自动化渗透测试领域的应用潜力。严禁任何形式的人工渗透,须通过 Agent 驱动赛事。 参赛者需以大语言模型(LLM)为核心,构建自主渗透智能体,在隔离环境中完成从漏洞发现、利用执行到攻击路径编排的全流程验证。
主赛场采用阶梯式解锁机制,需依次突破四大赛区:
| 赛区 | 主题 | 考察重点 | Flag 解锁阈值 |
|---|---|---|---|
| 第一赛区 | 识器·明理 | 20+ SRC 场景,自动化众测与主流漏洞发现 | 14 个 |
| 第二赛区 | 洞见·虚实 | 典型 CVE、云安全及 AI 基础设施漏洞 | 6 个 |
| 第三赛区 | 执刃·循迹 | 多层网络环境,多步攻击规划与权限维持 | 9 个 |
| 第四赛区 | 铸剑·止戈 | 基础域渗透,企业核心内网环境推演 | — |
解锁规则:智能体须在当前赛区达成指定的 Flag 提交阈值后,方可激活下一赛区的访问权限。
每道赛题设定基础分值,最终得分根据解题名次系数与提示系数进行调整:
| 解题名次 | 分值调整 |
|---|---|
| 第 1 名 | +20% |
| 第 2 名 | +10% |
| 第 3 名 | +5% |
| 第 11 名及以后 | -10% |
| 第 21 名及以后 | -50% |
| 第 31 名及以后 | -80% |
- 提示系数:查看漏洞提示将扣除 10% 分数
- 分值锁定:提交正确 Flag 后,该题得分即时锁定,后续其他选手的解题行为不会影响已获得的分值
LLMNOR Agent设计
Agent方案几经更迭,最初是想参考去年排名第四的chainreactor队伍的tinyctfer思路,用claude做领域相关的知识增强,在他的基础上做优化。由于他的极简设计,agent跑起来非常轻快,看他的源码和封装的工具就感觉我和他之间差了一个银河系,有两个工具甚至我没听过,我还处于无知且不自知的阶段 :)
他开源的代码是无法直接运行的,花了点时间修复了框架中存在的小问题之后,在xbow上试了试这个框架,发现远远无法达到他比赛的正确率,推测开源的不是比赛的版本,但是里面的设计思路还是非常值得学习的。
- 一切的行为都由jupyter 运行代码实现,方便后期复现做题过程
- 使用 caido+playwright截取流量,方便之后基于这个流量做fuzz
- note记事本保留重要信息,便于继续之前做题过程
- 提示词中写明了常规工具的调用方法,利于大模型构造出合理的探测指令
- tmux做会话管理,kill超时会话和读取shell内容,处理交互式shell
当然,也能注意到一些可以优化的地方:
- 过度依赖模型的思考和tooluse能力,本身没有对模型做任何的增强。
- 攻击技战术和漏洞知识全靠模型训练过程中本身对网络安全知识的记忆。训练数据cut off 之后的cve知识全都不知道。
- 缺少攻击范围约束,很容易跑偏到其他的非预期目标。
- 渗透测试过程的记忆管理只有简单的notebook和共享笔记本,对于上下文的压缩处理依赖于claude code 本身的控制。
- 依赖于MCP调用外部工具,上下文输出可能会超级长且较难观测异常。模型可能并不知道调用这个工具的正确指令是什么,经常构造出不符合工具输入形式的指令。
- codeAct模式想法很棒,但是总感觉少点什么,偶尔也略显笨重。
- 基于tmux实现的会话管理,send-key的实现不够高效,对于能实现阻塞的交互式shell,但是还可以继续优化。
- 完全依赖于模型对指令的遵守程度,没有针对行为的硬限制。
为优化这些问题,我最初朴素的想法是在这上面继续叠加skill和RAG,但是很快就发现被 claude code 束缚了手脚,一切工程都是围绕着服务 claude code ,比较难塞进去自己的想法。
于是思路从定制claude code变成根据需要自己手搓,按照常规的 ReAct 的思路自己实现一个Agent,按需加载各个领域的skill以增强模型在各个领域的能力。
使用plan-with-file 的思路实现更长任务状况下的任务树管理,缓解Agent做着做着就忘记初心的问题。
从古法编程迁移到vibe coding半年多了,自然我是不太会亲自写代码的,速度慢质量还不好。通过和 codex/claude code 反复聊天,生成了项目的实施规划和约束,让codex约束生成代码。
实施过程中也发现了不少问题:
- codex 和 claude code 总是常常忘记本项目是 uv 作为包管理器,反而会傻乎乎的 python直接运行,然后发现缺依赖再去手动安装。Solution:通过 CLAUDE.md的描述缓解。
- 大模型太想让程序运行起来了,过分谨慎,所以写了太多的错误处理,以至于程序事实上有地方没正常工作但是用户很难发现。Solution:提示写代码用 let it crash 风格,方便调试,这可以很大缓解这个现象。
- 战报会说谎,大模型也会。有时候大模型声称修复了某个问题,并给出 diff,但是事实上他并没有get到问题的 root cause。
- 大模型倾向于做最小化的修改,容易留下技术债务,和人类写代码的风格一模一样,在已有的💩山上拉一坨更大的💩。Solution:告诉大模型这是程序开发的初始阶段,应当随时调整重构,减少技术债务。当然这也就引出了下一个问题。
- 由于大模型写代码速度太快,每次变动大量代码,造成了大模型写出的代码人类几乎无法维护,只能靠大模型自己维护。没有经过review的代码等于负债,codex写的越多,负债越多。codex/claude code 这样激进的全托管状态写代码应用在生产环境中是个考验心脏的活动。
由于这个项目的一切都是大模型实现的,所以还需要花比写代码更多得多的时间去看懂程序的运行逻辑。而且当我有额外的需求时,大模型可能一次修改很多个文件,对人的理解能力是个不小的考验。 总体代码框架结构如下。
flowchart LR
U["用户目标<br/>Target"] --> M["主控循环<br/>src/main.py"]
MB["模型后端池<br/>src/model_backends.py"] --> M
M --> D["统一分发器<br/>execute_tool()"]
D --> E["执行层<br/>src/execution.py"]
D --> B["浏览器层<br/>src/browser.py"]
D --> R["检索层<br/>src/RAG.py"]
D --> W["记忆层<br/>src/memory.py"]
E --> T["真实工具与目标环境<br/>Kali / Network / Web"]
B --> T
R --> K["本地知识库<br/>skills / payloads / exploitdb"]
E --> FS["工作区产物<br/>memory / jobs / notebooks"]
B --> FS
W --> FS
R --> FS
数据流大致如下:
sequenceDiagram
participant U as User
participant L as run_agent_loop
participant MB as BackendPool
participant TR as Tool Runtime
participant WS as Workspace
U->>L: 提供 target
L->>WS: init_workspace()
L->>MB: load_backend_pool()
L->>L: _discover_tools() + _build_bootstrap_prompt()
loop ReAct 闭环
L->>MB: _request_completion_with_failover()
MB-->>L: tool call / text response
L->>TR: execute_tool(name, args)
TR->>WS: 写入 logs / traffic / notebooks / memory
TR-->>L: 返回执行结果
L->>WS: 更新 state / todo / findings
end
L->>WS: mission_complete() 写入 final_summary.md
L-->>U: 输出最终总结
在工具调用阶段提供了 TOOL列表(比如exec_cmd/write_file/read_file/session_manage/search_payload/flag_submit等等)给大模型,描述各种工具的用法。通过检测模型返回的消息中是否包含了[TOOL]区块检测消息类型,并且提取出执行的指令将返回结果以及恰当的其他上下文塞回大模型。
flowchart LR
M["模型输出"] --> S{"是否为 structured tool call"}
S -->|是| D["execute_tool(name, args)"]
S -->|否| P["_extract_pseudo_tool_call()"]
P -->|提取成功| D
P -->|提取失败| X["保留为普通文本响应"]
T1["执行类"] --> D
T2["文件 / 工作区类"] --> D
T3["观察类"] --> D
T4["检索类"] --> D
T5["任务语义类"] --> D
T6["浏览器类"] --> D
D --> E["src/execution.py"]
D --> MM["src/memory.py"]
D --> N["src/normalization.py"]
D --> R["src/RAG.py"]
D --> B["src/browser.py"]
D --> F["record_finding / mission_complete"]
记忆管理也是个众所周知的难题,这里假定模型上下文128k,每次请求保留前面n次会话内容,当到达某个阈值后做摘要。Opus 4.6是支持1M上下文的,但是这里没有采用,因为之前开发阶段按照DeepSeek的风格写的,并且是考虑到同时兼容多个不同厂商的大模型,比赛过程中如果api后端超时了做fallback,也就未能利用到Opus 4.6的这个feature。如果能用上1M的上下文应该能提升挺多能力,因为html文本通常都比较长,很容易塞满128k的会话,这也是这次比赛的一个遗憾了 :)
flowchart TD
A["启动 run_agent_loop()"] --> B["init_workspace(workspace)"]
B --> C["load_backend_pool()"]
C --> D["_discover_tools()"]
D --> E["读取 /memory/state.md 与 /memory/todo.txt"]
E --> F["_build_bootstrap_prompt()"]
F --> G["请求模型"]
G --> H{"模型输出"}
H -->|structured tool call| I["execute_tool(name, args)"]
H -->|普通文本| P["_extract_pseudo_tool_call()"]
P -->|提取成功| I
P -->|提取失败| X["保留文本并继续下一轮"]
I --> J["结果回灌消息与工作区"]
J --> K{"任务是否完成"}
K -->|否| G
K -->|是| Z["mission_complete / 退出循环"]
使用了 playwright+mitmdump+chromium的方案做中间流量的截取
sequenceDiagram
participant A as Agent
participant P as Playwright
participant M as mitmproxy
participant T as Target Web App
participant W as Workspace /jobs/browser_NAME
A->>P: start_browser_session(name, target_url)
P->>M: 浏览器流量走代理
M->>T: HTTP/HTTPS 请求
T-->>M: 返回页面/API 响应
M-->>W: 写入 traffic.mitm
A->>M: extract_traffic(name)
M-->>A: 请求摘要 + replay 建议
A->>M: replay_request(name, request_index, modifications)
M-->>A: 重放信息 / curl 建议
围绕claude code的harness
由于我自知LLMNOR的存在一些设计瓶颈,导致我对这套框架的信心不足,于是又把心思打到了claude上。
想法很直白,为 claude code 准备好所需的一切运行环境、任务描述等等信息,然后启动 claude code 执行渗透测试的任务,比赛过程中两套agent用互相协作,既保留LLMNOR的高度自由性,也保留claude code 的多Agent组织能力、记忆管理和长程任务规划能力。
此外claude code如果遭遇到了后端崩溃的问题也无法自行恢复,这些都需要外层的wrapper做兼容,与靶场环境交互也是由外层wrapper控制。两套agent协作方案是经典的黑板模式,这在决赛的时候也有其他队伍提到了这种协作方案。
模型选择
最初开发阶段一直是用的DeepSeek-V3.2模型,在xbow的正确率只有46/104(有几个题目build失败了,排除下题目本身的原因,最终正确率大约在0.5)。GLM-5.1也有尝试过,感觉在渗透测试上的能力和DeepSeek V3.2差不多。
比赛前几天看主办方的群里有中转站的广告,很早以前也听人说claude +Opus 4.6在渗透测试处于无敌状态,看得心里痒,于是我们也加入了Opus 4.6的阵营。
在实际测试Opus 4.6能力之前,我一直以为Agent的总体能力一半取决于harness设计,另外一半是模型能力。然而当我用Opus 4.6做了个多层内网渗透的靶场之后,我被震撼的说不出话,Opus 4.6在7分钟内完成了DeepSeek V3.2两个小时的进度。
而且,Agent中可以调用的RAG(技战术,CVE)、skill等等周围工程,Opus都不会去调用,全程自己构造利用代码,不需要我提供的会话管理,自己开一个nc监听反弹shell;不需要搭建代理做内网扫描,Opus写好脚本传到靶机,然后rce执行这个脚本;甚至能多个位置的漏洞组合利用构造rce。看了Opus 4.6做题的过程,我为先前的无知感到抱歉,感觉模型能力占9成了。
另外在比赛最后一天Opus 4.7发布,当时切换到了Opus 4.7,但是没有明显感觉在渗透测试能力的提升,可能还需详细的bench。
RAG设计
最初我认为RAG是个逃不开的问题,未添加RAG时,DeepSeek V3.2在xbow上仅仅能解决寥寥几道题,加入了RAG之后瞬间暴涨到几十个题目。RAG对模型能力的提高是立竿见影的。
但是,在我用了Opus 4.6之后这个想法发生了改变,基模记忆足够好的情况下,可能RAG的增量会没那么明显,反而可能会因为过多杂乱信息注入分散模型注意力。DeepSeek参数685B,激活量37B,而传言中Opus 4.6参数大约2T,激活量超过100B。参数量带来的模型能力提升是肉眼可见的。
尽管如此,我们还是准备了一系列的RAG数据供大模型按需调用,RAG是一个复杂的工程,有好产品队伍的应该不太会拱手送给主办方,大家比赛中用到的设计估计都差不太多。
在单机/无GPU/磁盘空间局促这三个限制下,做高质量的RAG堪比螺蛳壳里做道场。我们融入了技战术、CVE漏洞描述、指纹匹配、EXP库等等内容,具体方案细节是大家在互联网上都能检索到的混合检索那一套,此处按下不表。
比赛趣事
四处乱打
比赛后半程的某一天,主办方说我们的主机在针对全球随机的目标发起ssh密码枚举攻击,怀疑我们是捣乱的,于是停掉了我们的机器 :(
最初我怀疑是定义的scope不够苛刻,因为我只在任务的spec描述中限定只能攻击 172.16.0.0/12,192.168.0.0/16,10.0.0.0/8的目标,而这是基于提示词的软限定,大模型可以选择不遵守这条规定;更健壮的方案应当是硬限定,在模型执行的bash命令之前加一个wrap,审计每一条执行的命令是否合法,但是这显然开发工作量稍微有点大,不太适合我们的场景。
我们分析了事故期间的日志,没有发现任何和枚举公网目标相关的大模型聊天记录,我们猜测可能是遭到了供应链攻击。依赖包中存在供应链攻击前科的是litellm,但是我们锁版本litellm 1.81.16,这个版本暂时没有已知的供应链攻击。继续详细检查了uv.lock的其他依赖,然而依赖的pip包没有已知的供应链攻击影响到的版本。
kali 软件源仓库存在供应链攻击的风险较小,风险较大的是调用的一些 Golang 的第三方库(Third-Party libraries,TPL),但基本都是1000星星以上的GitHub 项目,如果出现供应链攻击了那估计又是一个大新闻。
最后也不知道四处攻击的原因,在决赛那天主办方还cue了一次这事儿 :(
神秘路由
倒数第二天发现我们的Agent反复开关题目,日志中也大面积的无法访问题目,于是我们怀疑靶场出问题了,不少队伍在反映大模型网关偶现超时。
因为前几天也遇到了这个情况,只是一段时间问题会消失。但是这一次反复出现,几乎无法做题了,于是我们询问赛方是否是靶场环境的问题,但是得到的回复是其他队伍都正常。
含泪把环境停下来,开始漫长的分析。

发现我们的环境中多了一条神秘的路由 10.0.163.0/24。
经过和赛方多次沟通和仔细分析,我们发现问题的根源可能是我们的模型误操作而不是平台故障。在之前的解题过程中,模型可能发现目标题目不通(日志中看到很多次,目测是题目还没完全启动就访问目标了),也可能是发现了新的内网网段,在没有经过完全验证的情况下自行添加了路由,导致预期之外的流量被错误地转发,使得访问题目失败。
我们的任务部署在 Docker 容器中,每次运行时都会启动一个全新的容器,以避免非预期的影响。为了让容器内能够执行网络扫描任务,我们赋予了容器 CAP_ADMIN 权限;同时,为了支持更自由的端口映射和反向 shell 回连,我们又赋予了容器 --network host 权限。这两者的组合赋予了容器对宿主机网络栈的控制权限。
于是我们的宿主机就可能因为大模型的有趣想法出现了10.0.163.0/24 dev eth0 proto kernel scope link src 10.0.163.100 这条非预期的路由。
要缓解这个问题,我们肯定不能主动去限制大模型操纵宿主机的路由表,因为在某些特定场景(如VPN环境),添加路由表实现网络跨越是完全合理的行为。而且非预期的添加路由只是其中一种场景,要解决这类破坏性调整存在的风险,我们需要提醒大模型在进行可能带来破坏性后果的操作时,确保留下恢复措施和现场备份。
最后,如果大模型的执行任务被打断,且没有足够时间完成恢复操作,恢复措施应包括编写修改说明文档和修复脚本,以便能够在未来恢复正常操作。
线下决赛见闻
不得不说主办方的李鑫是个很有趣的人,把决赛选在了live house。一群黑客坐在下面看大神在舞台上讲Agent设计,讲最新攻击思路,偶尔还有歌手上去一展歌喉,属实有趣,很符合外界对黑客洒脱自由的定位(我是去打酱油的,算不上黑客)。
几个公司的方案明显比我们设计的成熟很多,完善的分层结构,多Agent分工设计,丰富的RAG知识库技战术,大量的经过验证的CVE利用代码。
京东线上第3的成绩,线下答辩完之后变成了第4,属实有点惨了。让我想起来多年以前的悲惨经历,datacon2022线上比赛排名3/662,对应二等奖,然而答辩完之后降了一名,变成了三等奖,痛失一大坨奖金 :(
清华的方案和我们一样的简单,单Agent设计,外加一大堆知识库做RAG。但是他们前两天速度嘎嘎快,把我给震惊了。
泪笑的方案和去年相似,不干预模型本身的认知,只提供协作环境,维护攻击进展的图结构。这个方案的疗效相当好,他是唯一一个做完了所有题目的队伍,但是个人认为不提供RAG感觉很难解决模型知识截止这个硬伤。
LLM中转站注入恶意指令的攻击面影响还是太大了,即使管理员没有恶意的想法,但是如果中转站框架,如new-api本身存在漏洞,那么依然存在巨大的攻击面,让所有的用户执行恶意的tool也就不难实现了,再往上可以是模型原始供应商返回结果被注入恶意指令,那么影响所有的大模型用户。上个月new-api是stripe 空密钥导致的恶意充值漏洞,下一次可能就是rce了,这种供应链攻击风险无处不在,毕竟我们这也搞供应链漏洞分析 :)
很多队伍同时用多个厂商的大模型并行做题,我们还是太保守了,只做了agent并发,没做api并发。这要做个两两组合那资金消耗不敢想啊 :)
跋
Agent在纯内网的目标环境中全力运行,我在工位玩溜溜球,不时听到队友说又做出来一题,这种感觉真的非常奇妙,像在打比赛又像在摸鱼,被大模型带飞的感觉是真的太棒了。当然坏处是大模型可能会飞太远失控了需要停下来维护 :)
比赛到后期的阶段,我们才逐渐知道几个较难题目的大致构造和考点,最终在无人介入的场景下拿到46个FLAG。人还没看到题目长啥样,大模型已经吭哧吭哧把题目做出来了,大模型还是太有实力了,古法CTF已经是过去时了。
在两年前我跟同学聊天还说全自动漏洞利用咋可能这么简单,距离我们还很遥远,但这梦幻的场景我们真的看到了,而且亲手实现的,未来真的来了。
回头想想,调用大模型API与大模型研究之间的关系是什么,增量都是大模型本身带来的。我们只是围绕某个目标,为大模型设计了一点点提示罢了 :)
这次的题目总体难度不是特别高(当然我肯定人工做不出这么多哈哈),也不涉及复杂的源代码审计漏洞挖掘,所以比赛的效果总体挺好,第一天就有队伍差点把原定5天的题目全做完了。
AI做题真的太快了,AI进步速度也真的太快了。两年前我还戏谑说,GPT-4说的都是啥乱七八糟的你也敢信,现在我只能是肃然起敬 :)
感谢各位队友,团队协作的感觉很棒!
感谢腾讯举办的此次比赛,非常有意思!
鸣谢朱老师对我们的大力支持,欢迎大家报考朱老师的研究生 ^_^
Les1ie
2026.5.4