文章总结: 本文详细介绍了基于CursorAgent实现的Web未授权访问自动化扫描Skill方案。该方案结合ChromeDevToolsMCP进行浏览器上下文API采集与探测,配合Python脚本实现正则提取、字典补全、参数矩阵构建及批量fetch(credentials:omit)无凭证请求测试,有效解决了SPA应用中JS未暴露接口漏测、带参接口误判及跨域CORS失败等痛点,最终输出标准化Markdown报告,并在多个站点实践中验证了其有效性。 综合评分: 86 文章分类: WEB安全,渗透测试,安全工具,安全开发
Web 未授权访问自动化扫描 Skill 实现
小猴 小猴
只会看监控的实习生
2026年7月25日 08:00 广东
在小说阅读器读本章
去阅读
一、项目背景
1.1 业务场景
在企业 Web 应用(Vue/React SPA + 前后端分离)的安全评估中,未授权访问与 IDOR(不安全的直接对象引用) 是最常见、也最容易被漏测的一类问题。典型表现:
- 前端 JS 打包后接口分散,人工从 Burp 历史里逐条重放效率低;
- 部分接口 不在 JS 静态文本中,只在运行时或字典命名规则下才能推断;
- 带参接口(如
?id=1)无参探测返回 400/500,容易被误判为「安全」; - 同一产品存在 多个 API 域名(读/写/管理),跨域探测必须用浏览器上下文,Python
requests常遇 SSL/CORS 失败。
1.2 痛点来源(真实漏报案例)
在对 某 SPA 业务站点(spa-demo-a.example.com)的早期手工/抽样测试中,曾出现:
| 接口 | 现象 | 根因 |
| — | — | — |
| /integral/listIntegralOrders | 未授权返回 116 条订单 | JS bundle 中无此路径 ,抽样测试未覆盖 |
| /address/getAddressInfo?id=2 | 未授权返回手机号、地址 | JS 中有路径但 未拼 id 参数 测试 |
这两例直接推动了「全量覆盖 + 字典补全 + 参数矩阵」三项硬性要求,并最终沉淀为 Skill。
1.3 设计目标
用户只需提供:
测试 https://xxx.com/#/login 未授权账号 手机号 / 密码(可选)
Agent 自动完成:采集 → 建矩阵 → 浏览器内无凭证探测 → 输出 {域名}.md 报告。
二、整体架构
┌─────────────────────────────────────────────────────────────┐│ 用户输入:target_url + 可选账号 │└──────────────────────────┬──────────────────────────────────┘ ▼┌─────────────────────────────────────────────────────────────┐│ Cursor Agent + web-unauth-scan SKILL.md(编排流程) │└──────────────────────────┬──────────────────────────────────┘ ▼ ┌──────────────────┴──────────────────┐ ▼ ▼┌───────────────────┐ ┌───────────────────────┐│ Chrome DevTools │ │ Python unauth_scan.py ││ MCP │ │ ││ · navigate_page │ │ · extract_apis ││ · list_network │ │ · build_matrix ││ · get_network_req │ │ · browser_scan_js_fn ││ · evaluate_script │◄─────────────│ · classify ││ (fetch omit) │ batch_*.js │ · render_report_md │└───────────────────┘ └───────────────────────┘ ▼ ┌────────────────────────┐ │ {域名}.md + result.json │ └────────────────────────┘
分工原则:
| 层 | 职责 |
| — | — |
| MCP 浏览器 | 登录、抓 Network/JS、在页面上下文执行 fetch(credentials:'omit') |
| Python | 正则提取 API、字典补全、参数变体、批量脚本生成、判定逻辑、Markdown 报告 |
| Skill 文档 | 约束 Agent 行为:禁止抽样、禁止 DELETE、输出格式、脱敏规则 |
三、实现过程
3.1 阶段一:Prompt 方法论(js.md)
最初以 195 行 Markdown 提示词 定义完整工作流,不依赖固定代码:
- Chrome MCP 登录并采集 XHR/Fetch + Script;
- 多源合并提取 API(Network / JS 正则 / 字典补全);
- 按路径语义拼参(
id=1/2、page=1&pageSize=10); - 全量
evaluate_script+credentials:'omit'; - 按
retCode、敏感字段输出报告。
关键约束(写入提示词,后迁入 Skill):
-
禁止只测 20 条样本;
-
无参 500 不代表有参安全;
-
retCode: 1001+
data需重点审查(部分业务后端的成功码约定)。
3.2 阶段二:脚本化(unauth_lib.py → unauth_scan.py)
将重复逻辑固化为 Python,避免 Agent 每次重写正则与判定:
| 模块 | 功能 |
| — | — |
| extract_apis() | 从 JS 文本正则提取 /api/*、/v1/* 等路径 |
| dict_complete() | 按模块前缀补 list、page、detail、getXById |
| build_variants() | 按路径名生成 GET/POST + query/body 变体 |
| build_matrix() | 合并 JS + Network + 字典 → 全量 cases[] |
| browser_scan_js_function() | 生成可在 MCP 中执行的 async fetch 批处理 JS |
| classify() | 统一漏洞判定(支持 retCode 与嵌套 status.code) |
| render_report_md() | 输出标准 {域名}.md |
后期将 5 个分散脚本(render_report.py、merge_and_report.py 等)合并为单一入口 unauth_scan.py,提供 CLI 子命令:
python unauth_scan.py merge --domain boss-demo-c.example.compython unauth_scan.py render result.json boss-demo-c.example.com.mdpython unauth_scan.py build-matrix --domain admin-demo-b.example.com
3.3 阶段三:Cursor Skill 封装
在 .cursor/skills/web-unauth-scan/SKILL.md 中声明:
-
触发词
:「未授权扫描」「测未授权接口」+ 登录页 URL;
-
输出约定
:工作区根目录
{域名}.md; -
中间产物
:
.unauth-scan/{域名}/matrix.json、batch_*.js、result_*.json; -
安全约束
:不写 git 密码、报告脱敏手机号。
3.4 阶段四:批量执行与站点适配
| 问题 | 解决方案 |
| — | — |
| Python 直连目标域名 SSL EOF | 强制浏览器内 fetch |
| MCP 单次 payload 过大 | 每批 150~180 条,生成 batch_00.js … |
| 站点 B 跨域 API 前缀 /api/v1/web/ | Network 路径优先 + 站点级补判处理 status.code=401 |
| 站点 C code=001108 | 站点级 reclassify.py 补判 + 手工验证 project/config POST body |
批量调度示例(run_batches.py)通过 MCP Python SDK 循环调用 evaluate_script:
await session.call_tool("select_page", {"pageId": 4})result = await session.call_tool("evaluate_script", { "function": fn, # batch_NN.js 全文"filePath": str(out),})
四、核心代码块
4.1 API 提取与字典补全
PATTERNS = [ re.compile(r'["\'](/api/[^"\']+)["\']'), re.compile(r'["\'](/v\d+/[^"\']+)["\']'), re.compile(r'["\'](/admin/[^"\']+)["\']'), re.compile(r'["\'](/[a-zA-Z][a-zA-Z0-9_]*/[a-zA-Z0-9_/\-]+)["\']'),]defdict_complete(apis: set[str]) -> set[str]: modules = {module_prefix(p) for p in apis if"/"in p.strip("/")} extra = set() for m in modules: cap = m[0].upper() + m[1:] if m else""for suffix in [ f"/{m}/list", f"/{m}/page", f"/{m}/detail", f"/{m}/get{cap}ById", f"/{m}/listPage", f"/{m}/list{m.capitalize()}Orders", ]: extra.add(suffix) return extra - apis
4.2 参数变体生成
defbuild_variants(path: str) -> list[dict]: name = path.lower() # get*Info / *Detail → id=1, id=2, 无参if re.search(r"get.*info|get.*byid|detail", name): return [ {"method": "GET", "query": f"id={i}", "body": None, "label": f"id={i}"} for i in (1, 2) ] + [{"method": "GET", "query": "", "body": None, "label": "none"}] # list* / *Page → 分页 GET + POST bodyif re.search(r"list|page|logs", name): return [ {"method": "GET", "query": "page=1&pageSize=10", "body": None, "label": "page"}, {"method": "GET", "query": "", "body": None, "label": "none"}, {"method": "POST", "query": "", "body": {"page": 1, "pageSize": 10}, "label": "post_page"}, ] ...
4.3 浏览器批探测 JS(MCP 执行体)
Python 生成如下结构,由 Chrome 在 已打开目标页 的上下文中执行:
async () => { const cases = [ /* host, path, method, query, body */ ]; const LOGIN_OK = /1002|请登录|未登录|token不能为空/i; functionverdict(body, status) { /* 与 Python classify 对齐 */ } const out = []; for (const c of cases) { const url = c.host + c.path + (c.query ? '?' + c.query : ''); const opts = { method: c.method, credentials: 'omit', headers: { Accept: 'application/json' } }; if (c.body) { opts.headers['Content-Type']='application/json;charset=UTF-8'; opts.body=JSON.stringify(c.body); } const r = await fetch(url, opts); const text = await r.text(); out.push({ path: c.path, method: c.method, status: r.status, verdict: verdict(text, r.status), ... }); } return out;}
credentials: 'omit' 等价于 不带 Cookie 的未授权请求,是整套方案的核心探测方式。
4.4 漏洞判定(多后端格式兼容)
defclassify(body: str, status: int) -> str:# HTTP 层if status in (401, 403): return"正常" j = json.loads(body) # 嵌套 status 格式: {"status":{"code":401,"msg":"token不能为空"},"data":null} st = j.get("status") ifisinstance(j.get("status"), dict) else {} rc = str(j.get("retCode", j.get("code", st.get("code", "")))) msg = str(j.get("retMsg", j.get("message", st.get("msg", "")))) if st.get("code") in (401, 403) or LOGIN_OK.search(msg): return"正常"# retCode 格式: retCode 1001 + dataif rc in ("1001", "0", "200") and j.get("data") isnotNone: if _has_sensitive_data(j.get("data")): return"高危"return"待复核"# 或 404/405/缺参/异常
4.5 报告生成
defrender_report_md(target_url, domain, hosts, stats, results, login_note="", note="") -> str: high = [r for r in results if r.get("verdict") == "高危"] # 输出:基本信息 → 统计摘要 → 高危表 → 全量附录(可折叠)
五、核心提示词
5.1 Skill 触发描述(SKILL.md frontmatter)
name:web-unauth-scandescription:>- 对指定 Web 登录页做 API 提取与全量未授权访问测试,输出 Markdown 报告到 {域名}.md。 用户提供测试 URL + 可选账号密码即可。使用 Chrome DevTools MCP 采集 JS/Network, 浏览器内 fetch(credentials:omit) 批量探测。用户说「未授权扫描」「测未授权接口」、 给出登录页 URL 和账号时使用本 skill。
5.2 用户一句话调用示例
测试 https://boss-demo-c.example.com/#/login 未授权
或带账号:
测试 https://admin-demo-b.example.com/#/login 未授权账号 13800138000 / ******
5.3 方法论提示词摘录(js.md)
角色定义:
你是一位安全研究员,负责自动化挖掘 Web 应用的未授权访问漏洞。
覆盖率硬约束:
对第二步清单中的每一个接口都测,不得只测「代表性样本」。带参接口:无参 + 有参都要测(无参 500/400 不代表有参也安全)。
字典补全示例:
/integral/listIntegralOrders ← JS 可能只有 listIntegralRules/address/getAddressInfo?id=1 ← 带参详情类/{module}/list*、/get*、/detail*、/page*
高危判定:
无凭证返回 retCode: 1001(或业务 success)且 data 含敏感字段(姓名、手机、地址、订单…)HTTP 200 + JSON 列表 total > 0 且无登录提示
经验教训(必须写入执行逻辑):
- JS 提取 ≠ 完整 API 面,必须字典补全
- 抽样测试会漏报
- 带参接口必须拼参
- retCode 1001 + data 要重点审查
- 读写在不同域名,写域名也要测只读 list/get
5.4 Agent 执行检查清单(Skill 内嵌)
| 步骤 | MCP / 脚本 | 禁止项 |
| — | — | — |
| 打开登录页 | navigate_page | — |
| 可选登录 | fill + click | 登录失败不中断 |
| 抓 JS | get_network_request → .unauth-scan/{域名}/ | — |
| 建矩阵 | build_matrix() | 禁止跳过字典补全 |
| 全量探测 | evaluate_script(batch_NN.js) | 禁止抽样 |
| 出报告 | unauth_scan.py merge | 禁止明文密码入报告 |
六、目录结构
.cursor/skills/web-unauth-scan/├── SKILL.md # Agent 编排说明├── report-template.md # 报告结构参考└── scripts/ └── unauth_scan.py # 库 + CLI(唯一主脚本).unauth-scan/{域名}/├── app.network-response # 抓取的 JS├── matrix.json # 全量 cases├── batch_00.js … batch_NN.js├── result_00.json … result_NN.json├── run_batches.py # MCP 批量调度(可选)└── reclassify.py # 站点专用补判(可选){域名}.md # 最终报告(工作区根目录)
七、实践结果
八、已知局限与改进方向
| 局限 | 说明 | 改进方向 |
| — | — | — |
| 字典补全噪声 | 无 /api/v1 前缀的路径跨域 CORS 失败 | REST 站点仅补全 Network 真实前缀 |
| 特殊 POST body | project/config 需 {url:...} 非 {} | Network 抓包还原 body 模板 |
| 判定漏报 | appToken 不在默认敏感字段规则 | 扩展 SENSITIVE 含 appToken |
| 浏览器 Cookie 污染 | 页面已有登录 Cookie 时 fetch 仍带 cookie | 隔离上下文或专用无痕页 |
| 写操作过滤 | 仅跳过 path 含 delete/save 等 | 维持只读策略,避免误伤生产 |
九、如何使用
- 在 Cursor 中确保 Chrome DevTools MCP 已连接(
chrome-devtools-mcp --autoConnect) - 对 Agent 说:
使用 web-unauth-scan,测试 https://目标域名/#/login 未授权
- 等待生成
{域名}.md - 可选本地合并:
python .cursor/skills/web-unauth-scan/scripts/unauth_scan.py merge --domain 目标域名
十、总结
web-unauth-scan 将 安全测试方法论(js.md)、可复用 Python 引擎(unauth_scan.py)与 Cursor Agent 编排(SKILL.md)结合,形成「一句话输入 → 全量未授权报告」的闭环。其核心价值不在于替代 Burp,而在于:
-
强制全量 + 拼参
,避免抽样与无参误判;
-
浏览器内探测
,绕过 SSL/CORS 与跨域 API 限制;
-
字典补全
,覆盖 JS 未声明的隐藏接口;
-
标准化报告
,便于归档与复测对比。
在上述三个示例系统的实践中,共确认 4 条高危未授权/信息泄露 路径,验证了该 Skill 在真实业务中的有效性。
原文链接:https://forum.butian.net/ai_security/218
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:只会看监控的实习生 小猴 小猴《Web 未授权访问自动化扫描 Skill 实现》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









![[渗透测试]一次从XSS到任意文件读取](/images/random/titlepic/11.jpg)
评论