js-analysis · diff

git:20260620.aa6add7 to git:20260709.a882698

22 added, 610 removed. Audit B to A.

---
name: js-analysis
description: >
- JS 文件全量采集与深度分析。双通道(雪瞳+自主探测)下载 JS,本地按域名组织,
- 逐文件深度读取提取完整请求签名(URL + Method + Content-Type + 参数名 +
- 是否必填 + Auth要求),构建接口-参数需求映射表,作为后续所有阶段的输入基础。
+ Extract client-side attack surface from JS and sourcemaps without
+ dictating the order of investigation.
metadata:
- tags: "js-analysis,endpoint-extraction,credentials,spa,request-signature,parameter-mapping"
- category: "offensive-security"
- ---
-
- # JS Analysis — 全量采集 + 深度分析 + 请求签名提取
-
- > 核心原则:JS 文件是最大的接口信息来源。不走完这一步,绝不去 Fuzz。
- >
- > **你的优势**:可以同时加载并深度阅读所有 JS 文件,这是人类做不到的规模化分析。
-
- ---
-
- ## ⛔ 强制规则(不可跳过)
-
- ```
- 规则 1: 禁止只看前 N 行 —— 必须读取每个 JS 文件的完整内容
- 规则 2: 禁止只搜索关键词 —— 搜索完成后必须完整阅读有命中的文件
- 规则 3: 禁止跳过"看起来不重要"的 JS 文件 —— 每个 .js 文件都可能含 API 调用
- 规则 4: 提取粒度必须到参数级别 —— 不只是 URL,要完整的请求签名
- 规则 5: 必须保存到本地 —— 每个域名一个目录,文件用原始路径命名
- ```
-
- ---
-
- ## 1. 双通道 JS 采集
-
- ### 通道 A:雪瞳爬虫(自动化采集)
-
- ```
- 雪瞳负责:
- □ 爬取目标所有页面,自动拦截 .js 请求
- □ 触发懒加载路由(SPA 的每个 hash 页面点一遍)
- □ 多角色场景:登录不同角色后分别采集
- □ 输出:所有的 .js URL 清单 + 请求/响应记录
-
- 雪瞳的输出保存到:
- downloaded/{domain}/
- ├── _js_urls.txt # 所有 JS URL 清单(去重后)
- ├── _response_log.jsonl # 每个请求的 HTTP 日志
- └── js/ # 按原始路径组织的 JS 文件
- ```
-
- ### 通道 B:自主探测(补充采集)
-
- ```bash
- # 1. waybackurls 拿历史 JS(可能包含旧版本/未删除的测试端点)
- curl -s "https://web.archive.org/cdx/search/cdx?url=*.${TARGET}/*&output=text&fl=original&collapse=urlkey" \
- | grep '\.js$' | sort -u > wayback_js_urls.txt
-
- # 2. 浏览器 DevTools → Sources → 全部保存
- # 按 Ctrl+Shift+S 另存每个 JS 文件
-
- # 3. 从页面 HTML 中提取 <script src="">
- curl -s https://${TARGET} | grep -oP 'src="([^"]+\.js[^"]*)"' | sort -u
-
- # 4. 合并去重
- cat _js_urls.txt wayback_js_urls.txt page_js_urls.txt | sort -u > all_js_urls.txt
- ```
-
- ### 本地存储规范
-
- ```
- downloaded/{domain}/
- ├── _js_urls.txt # 全量 JS URL 清单
- ├── _analysis_summary.md # JS 分析总结
- ├── _endpoint_params.json # ★ 核心产出:接口→参数需求映射表
- ├── _secrets_found.json # 硬编码凭据/密钥
- ├── _hash_routes.txt # SPA Hash 路由清单
- ├── _login_links.json # 登录入口清单(请求测试账号时必须附链接)
- └── js/
- ├── static_js_app.js
- ├── static_js_chunk-vendors.js
- ├── static_js_api.js
- └── ...
- ```
-
- ---
-
- ## 2. 逐文件深度读取流程
-
- ### Step 1: 获取全量 JS URL 清单
-
- ```
- 从雪瞳输出 + wayback + 页面提取 → all_js_urls.txt(去重排序)
- ```
-
- ### Step 2: 逐个下载到本地
-
- ```bash
- mkdir -p downloaded/${TARGET_HOST}/js/
- while IFS= read -r url; do
- # 用 URL 路径作为文件名
- fname=$(echo "$url" | tr '/:.?=&' '_' | sed 's/_\+/_/g')
- curl -s "$url" -o "downloaded/${TARGET_HOST}/js/${fname}.js"
- done < all_js_urls.txt
- ```
-
- ### Step 3: 逐个文件深度读取(核心步骤)
-
- **对每个 .js 文件执行以下操作(不可跳过):**
-
- ```
- □ 完整读取文件内容(不是只看前几行)
- □ 提取所有 API 调用及其完整请求签名
- □ 提取所有硬编码字符串(重点:密钥、token、URL)
- □ 提取 SPA 路由定义(Vue Router / React Router)
- □ 提取加密函数签名(CryptoJS / Web Crypto)
- □ 提取请求拦截器/中间件逻辑(axios interceptor / fetch wrapper)
- □ 记录文件大小和大致功能分类
- ```
-
- ---
-
- ## 3. 完整请求签名提取(最重要)
-
- ### 不只是搜 URL,要还原完整的 HTTP 请求
-
- 每发现一个 API 调用,提取以下完整信息:
-
- ```
- 必提字段:
- □ HTTP 方法: GET / POST / PUT / DELETE / PATCH
- □ 完整 URL: (含路径参数占位符,如 /api/users/:id)
- □ Content-Type: application/json / multipart/form-data / etc.
- □ 需要的参数名: 从请求体构造逻辑中提取
- □ 参数是否必填: 判断是否有默认值或条件判断
- □ 认证方式: Authorization: Bearer / X-API-Key / Cookie / 无
-
- 可选提取:
- □ 响应处理逻辑: 从 .then() / await 后提取预期返回字段
- □ 错误处理: 错误回调中暴露的错误信息格式
- □ 请求拦截器逻辑: 公共参数注入(timestamp/sign/token)
- ```
-
- ### 3a. fetch() 调用 → 完整签名提取
-
- ```javascript
- // 源码示例:
- fetch('/api/user/info', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': 'Bearer ' + token
- },
- body: JSON.stringify({
- userId: userId,
- orgId: orgId
- })
- })
-
- // 提取结果:
- // ┌─────────────────────────────────────────────────────────┐
- // │ POST /api/user/info
- // │ Content-Type: application/json
- // │ Authorization: Bearer <token>
- // │ 参数: userId (必填), orgId (必填)
- // │ 来源文件: static_js_app.js: 行号
- // └─────────────────────────────────────────────────────────┘
- ```
-
- ### 3b. axios() 调用 → 完整签名提取
-
- ```javascript
- // 源码示例:
- axios.post('/api/order/list',
- { buyerId, startTime, endTime, page: 1, pageSize: 20 },
- { headers: { 'X-Requested-With': 'XMLHttpRequest' } }
- )
-
- // 提取结果:
- // ┌─────────────────────────────────────────────────────────┐
- // │ POST /api/order/list
- // │ Content-Type: application/json (axios 默认)
- // │ 参数: buyerId (必填), startTime (必填), endTime (必填),
- // │ page (可选, 默认1), pageSize (可选, 默认20)
- // │ Headers: X-Requested-With: XMLHttpRequest (自动)
- // └─────────────────────────────────────────────────────────┘
- ```
-
- ### 3c. $.ajax / XMLHttpRequest → 完整签名提取
-
- ```javascript
- // jQuery:
- $.ajax({
- url: '/api/export/user',
- type: 'POST',
- contentType: 'application/json',
- data: JSON.stringify({ userIds: userIds, fields: ['name', 'email'] }),
- beforeSend: function(xhr) { xhr.setRequestHeader('X-CSRF-Token', csrf); }
- })
-
- // 提取结果:
- // ┌─────────────────────────────────────────────────────────┐
- // │ POST /api/export/user
- // │ Content-Type: application/json
- // │ 参数: userIds (必填, 数组), fields (必填, 数组)
- // │ Auth: X-CSRF-Token header (来自 CSRF token)
- // └─────────────────────────────────────────────────────────┘
- ```
-
- ### 3d. API 封装函数 → 提取基础配置
-
- ```javascript
- // 很多项目会封装统一的请求函数:
- const apiClient = axios.create({
- baseURL: 'https://api.target.com/v2',
- timeout: 10000,
- headers: {
- 'Content-Type': 'application/json',
- 'X-API-Key': apiKey
- }
- });
-
- // 提取结果:
- // ┌─────────────────────────────────────────────────────────┐
- // │ Base URL: https://api.target.com/v2
- // │ 全局 Headers: Content-Type: application/json
- // │ X-API-Key: <从变量 apiKey>
- // │ 超时: 10000ms
- // │ → apiKey 的来源需要继续追踪
- // └─────────────────────────────────────────────────────────┘
- ```
-
- ### 3e. GraphQL 调用 → 提取
-
- ```javascript
- // 源码:
- const { data } = await client.query({
- query: gql`
- query GetUser($id: ID!) {
- user(id: $id) { id name email role }
- }
- `,
- variables: { id: userId }
- })
-
- // 提取结果:
- // ┌─────────────────────────────────────────────────────────┐
- // │ POST /graphql (推断)
- // │ Content-Type: application/json
- // │ Query: GetUser($id: ID!)
- // │ 参数: id (必填, ID类型)
- // │ 返回字段: id, name, email, role
- // └─────────────────────────────────────────────────────────┘
- ```
-
- ---
-
- ## 4. 构建 _endpoint_params.json(核心产物)
-
- 将从所有 JS 文件中提取的请求签名汇总为结构化映射表:
-
- ```json
- {
- "/api/user/list": {
- "method": "POST",
- "content_type": "application/json",
- "auth": "Bearer",
- "params_required": ["page", "pageSize"],
- "params_optional": [],
- "source_files": ["static_js_app.js"],
- "notes": "返回用户列表,含 userId, name"
- },
- "/api/user/info": {
- "method": "POST",
- "content_type": "application/json",
- "auth": "Bearer",
- "params_required": ["userId"],
- "params_optional": [],
- "source_files": ["static_js_app.js"],
- "notes": "返回用户详情,含 phone, email, orgId"
- },
- "/api/user/{id}/detail": {
- "method": "GET",
- "content_type": null,
- "auth": "Bearer",
- "params_required": ["id (路径参数)"],
- "params_optional": [],
- "source_files": ["static_js_chunk-vendors.js"],
- "notes": "路径 REST 风格"
- },
- "/api/order/list": {
- "method": "POST",
- "content_type": "application/json",
- "auth": "Bearer",
- "params_required": [],
- "params_optional": ["buyerId", "startTime", "endTime", "page", "pageSize"],
- "source_files": ["static_js_app.js"],
- "notes": "全部参数可选,不传返回全部订单"
- },
- "/api/export/user": {
- "method": "POST",
- "content_type": "application/json",
- "auth": "Bearer + X-CSRF-Token",
- "params_required": ["userIds", "fields"],
- "params_optional": [],
- "source_files": ["static_js_admin.js"],
- "notes": "管理功能,需 admin 权限"
- },
- "/api/admin/config": {
- "method": "GET",
- "content_type": null,
- "auth": "X-API-Key",
- "params_required": [],
- "params_optional": [],
- "source_files": ["static_js_admin.js"],
- "notes": "API Key 在响应中泄露,来源 /api/user/info"
- }
- }
- ```
-
- ---
-
- ## 5. 搜索关键词增强版
-
- **先全量搜索定位,再逐文件深度阅读。**
-
- ```
- 第一轮 — 定位 API 调用的所有变体:
- fetch( axios( axios.get( axios.post( axios.put(
- axios.delete( $.ajax( $.get( $.post( $.getJSON(
- XMLHttpRequest request( .get( .post( .put(
- createApi( useQuery( useMutation( useLazyQuery(
- graphql( gql` client.query( client.mutate(
- apolloClient createHttpLink
-
- 第二轮 — 定位 URL/路径:
- /api/ /v1/ /v2/ /v3/ /rest/
- /graphql /query /mutation /internal/ /admin/
- /manage/ /debug/ /actuator /swagger /druid
- baseURL: API_HOST API_BASE serverUrl endpoint:
- path: url: prefix: proxyUrl
-
- 第三轮 — 定位参数(确认哪些参数被传递):
- body: data: params: query: variables:
- JSON.stringify( formData new URLSearchParams
- ? & =
-
- 第四轮 — 定位认证信息:
- Authorization Bearer X-API-Key X-Auth-Token token
- apiKey secretKey access_token refresh_token csrf
- Cookie session withCredentials: true
-
- 第五轮 — 定位加密/签名:
- CryptoJS.AES CryptoJS.DES CryptoJS.enc CryptoJS.mode
- crypto.subtle createHmac md5( sha256(
- sign( signature timestamp + secret
- ```
-
- ---
-
- ## 6. 硬编码凭据提取增强
-
- ```javascript
- // 搜索以下模式 — 不只是搜索变量名,要读赋值语句的完整内容:
-
- // API 密钥 (云服务)
- // 特征: 以 AKID/LTAI/AKIA/APID/AIza 开头
- const accessKeyId = 'AKIDxxxxxxxxxxxxxxx';
-
- // JWT Secret / 签名密钥
- const JWT_SECRET = 'my_super_secret_key_12345';
- const SIGN_KEY = 'abcdefghijklmnop';
-
- // AES 密钥 + IV
- var key = CryptoJS.enc.Utf8.parse("1234567890123456");
- var iv = CryptoJS.enc.Utf8.parse("6543210987654321");
-
- // 第三方服务密钥
- const SMTP_PASS = 'email_password_here';
- const MAPS_API_KEY = 'AIzaSyxxxxxxxxxxxxx';
-
- // 数据库连接(罕见但致命)
- const dbUrl = 'mongodb://admin:password@internal-db:27017/prod';
-
- // OAuth 客户端密钥
- const clientSecret = 'oauth_client_secret_here';
- ```
-
- ---
-
- ## 7. SPA Hash 路由提取增强
-
- ```javascript
- // Vue Router:
- const routes = [
- { path: '/dashboard', component: Dashboard },
- { path: '/admin/users', component: AdminUsers, meta: { requiresAuth: true, roles: ['admin'] } },
- { path: '/admin/config', component: AdminConfig, meta: { requiresAuth: true } },
- { path: '/debug/logs', component: DebugLogs },
- ];
-
- // React Router:
- <Route path="/dashboard" element={<Dashboard />} />
- <Route path="/admin/users" element={<AdminUsers />} />
- <Route path="/internal/status" element={<InternalStatus />} />
-
- // 提取输出到 _hash_routes.txt:
- // /#/dashboard → 基础页面
- // /#/admin/users → 需 admin 角色
- // /#/admin/config → 需认证
- // /#/debug/logs → 无认证!(高危)
- // /#/internal/status → 无认证!(高危)
- ```
-
- 同时将包含 `login`、`signin`、`sso`、`oauth`、`auth`、`register`、`callback` 的路由、表单 `action`、跳转 `Location` 和 OAuth/SSO 授权 URL 写入 `_login_links.json`。后续任何 Credential Gate 请求账号时,必须从该文件选择最匹配的登录链接。
-
- **SPA 路由价值判断:**
- ```
- 优先级:
- P0: /admin/ /manage/ /debug/ /internal/ 路由(无认证约束时立即测试)
- P1: 需要特定角色但无后端校验的 admin 路由
- P2: 普通用户路由(可能触发新的 API 调用)
- ```
-
- ---
-
- ## 8. 请求拦截器分析(关键)
-
- 很多项目用 axios/fetch 拦截器自动注入公共参数。这部分逻辑会告诉你"每个请求自动带了什么"。
-
- ```javascript
- // axios 请求拦截器 → 全局注入的参数
- axios.interceptors.request.use(config => {
- config.headers['Authorization'] = 'Bearer ' + store.getters.token;
- config.headers['X-Request-Timestamp'] = Date.now();
- config.headers['X-Request-Sign'] = md5(config.url + Date.now() + SECRET);
- config.params = { ...config.params, _t: Date.now() };
- return config;
- });
-
- // 提取结果:
- // ┌─────────────────────────────────────────────────────────┐
- // │ 全局请求拦截器自动注入:
- // │ Authorization: Bearer <token> (认证)
- // │ X-Request-Timestamp: <unix_ms> (防重放)
- // │ X-Request-Sign: md5(url+ts+SECRET) (签名)
- // │ _t: <unix_ms> (查询参数, 防缓存)
- // │
- // │ → SECRET 需要从其他变量找到
- // │ → 签名算法: md5(url + timestamp + SECRET)
- // └─────────────────────────────────────────────────────────┘
- ```
-
- ---
-
- ## 9. JS 文件分类分析
-
- ```
- 对每个 JS 文件,分析完成后标记:
-
- □ 类型: vendor/lib / app / chunk / config / admin / login
- □ 大小: ____ KB
- □ 是否包含 API 调用: 是/否 (数量)
- □ 是否包含硬编码凭据: 是/否
- □ 是否包含加密逻辑: 是/否
- □ 是否包含路由定义: 是/否
- □ 是否包含请求拦截器: 是/否
- □ 优先级: P0(包含管理API) / P1(包含用户API) / P2(工具库) / P3(静态资源)
-
- 聚焦优先级:
- P0 文件: 逐行阅读,完整提取
- P1 文件: 搜索+上下文阅读
- P2 文件: 仅提取版本号(用于依赖扫描)
- P3 文件: 跳过(如 moment.js, lodash.min.js 等已知第三方)
- ```
-
+ tags: "js,spa,endpoint-extraction,sourcemap"
---
- ## 10. JS 分析检查清单 + 完整性追踪
-
- ### ⛔ 完成判定(Phase 0→1 Gate 的前置条件)
-
- ```
- 在宣称"Phase 0 完成"之前,_endpoint_params.json 的 _meta 节必须满足:
-
- □ js_files_collected > 0 (下载了 JS 文件)
- □ js_files_analyzed > 0 (逐个完整读取了文件)
- □ analysis_completeness ≥ 0.8 (至少 80% 的非第三方文件已分析)
- □ total_endpoints_extracted ≥ 3 (至少提取了 3 个 API 端点)
- □ 每个端点标注了 method 和 source_files ← ★ 必填
-
- 如果 _meta 不满足 → 不能进入 Phase 1
- ```
-
- ### 10a. _endpoint_params.json v2.4 完整格式
-
- ```json
- {
- "_meta": {
- "js_files_collected": 8,
- "js_files_analyzed": 7,
- "js_files_skipped": ["moment.min.js"],
- "skipped_reason": "confirmed 3rd-party: moment.js datetime lib",
- "analysis_completeness": 1.0,
- "files_detail": {
- "app.js": {
- "analyzed": true,
- "size_kb": 234,
- "api_calls_found": 12,
- "classification": "app",
- "priority": "P1",
- "notes": "主应用逻辑,含用户 API"
- },
- "chunk-vendors.js": {
- "analyzed": true,
- "size_kb": 1200,
- "api_calls_found": 5,
- "classification": "chunk",
- "priority": "P0",
- "notes": "含 axios 拦截器 + admin API 配置"
- },
- "admin.js": {
- "analyzed": true,
- "size_kb": 89,
- "api_calls_found": 15,
- "classification": "admin",
- "priority": "P0",
- "notes": "管理后台独立构建,含 /admin/* 接口"
- },
- "moment.min.js": {
- "analyzed": false,
- "size_kb": 72,
- "api_calls_found": 0,
- "classification": "vendor",
- "priority": "P3",
- "notes": "第三方日期库,确认跳过"
- }
- },
- "total_endpoints_extracted": 32,
- "total_secrets_found": 2,
- "total_routes_found": 8,
- "warnings": [],
- "generated_at": "2026-05-19T12:00:00Z"
- },
- "endpoints": {
- "/api/user/list": {
- "method": "POST",
- "content_type": "application/json",
- "auth": "Bearer",
- "params_required": ["page", "pageSize"],
- "params_optional": [],
- "source_files": ["app.js"],
- "notes": "返回 {userId, name, phone}"
- }
- }
- }
- ```
-
- ### 10b. JS 文件分析追踪清单
-
- ```
- 逐文件追踪(对 每个 JS 文件 必须如实标记):
-
- □ 文件: _________.js | 大小: ____ KB
- □ analyzed: true / false ← 是否已完整读取?(不只是搜索关键词)
- □ api_calls_found: ___ ← 提取到几个 API 调用?
- □ classification: app / chunk / admin / vendor / config / login / unknown
- □ priority: P0 / P1 / P2 / P3
- □ notes: _________
-
- 对 priority 分为 P0/P1 的文件:
- □ 必须 analyzed: true
- □ 必须提取所有 API 调用的完整请求签名
- □ 禁止 search-only(只搜索关键词不读完整内容)
-
- 对 priority 分为 P3 的文件(已知第三方库):
- □ 可以 analyzed: false
- □ 但必须在 skipped_reason 中说明理由
- □ 文件名必须在 js_files_skipped 列表中
-
- 聚焦优先级:
- P0 文件: 逐行阅读,完整提取(admin.js, 含 /admin/* 的 chunk)
- P1 文件: 搜索+上下文阅读(app.js, 业务逻辑 chunk)
- P2 文件: 仅提取版本号(vendor.js with axios config)
- P3 文件: 可跳过(moment.js, lodash.min.js 等已知第三方)
- ```
-
- ### 10c. ⛔ 禁止提交"未分析的空白清单"
+ # JS Analysis
- ```
- 以下情况视为 Phase 0 未完成:
+ ## Goal
- ❌ _meta 节缺失(无追踪数据)
- ❌ js_files_analyzed = 0(下载了但没读)
- ❌ analysis_completeness < 0.5(大部分文件没读)
- ❌ files_detail 为空(没有逐文件追踪)
- ❌ 所有文件都是 analyzed: false(虚假的"分析完成")
- ❌ P0 文件标记为 analyzed: false(管理接口文件被跳过)
- ❌ 端点 source_files = [](不知道从哪个 JS 文件来的)
- ❌ 端点 method = ""(不知道 HTTP 方法)
- ```
+ 从前端代码里恢复真实业务接口、参数、认证入口、路由、状态管理和隐藏功能,为后续攻击面分析提供结构化材料。
- ---
+ ## Tools / Inputs
- ## 11. 与后续阶段的接口
+ - JS、sourcemap、HTML、Network 请求、SPA 路由、localStorage/sessionStorage
+ - 输出:`_endpoint_params.json`、`_login_links.json`、`_secrets_found.json`
+ - 参考:`references/js-analysis-vulnforge.md`、`references/vue-spa-attacks.md`
- ```
- _endpoint_params.json → api_fuzz agent: 作为参数需求表
- _secrets_found.json → crypto_attack agent: 作为密钥字典输入
- _hash_routes.txt → api_fuzz agent: 测试隐藏页面
- JS 文件中的加密调用 → crypto_attack agent: 提取密钥+IV+算法
- JS 中的 baseURL → api_fuzz agent: 确定 API 根路径
- ```
+ ## Constraints
- ---
+ 1. 必须落盘并结构化输出,不能只口头总结。
+ 2. 提取粒度到 method、Content-Type、参数、auth、source file。
+ 3. 登录入口必须单独记录。
+ 4. 不把前端鉴权当后端鉴权。
+ 5. 发现新路由/chunk 后追加为新攻击面。
- ## 12. 禁止的行为
+ ## Chain Questions
- ```
- ❌ 只看搜索结果不读文件内容
- ❌ 只读 app.js 不读 chunk-*.js(懒加载文件经常藏管理接口)
- ❌ 只提取 URL 不提取请求体参数名
- ❌ 跳过 vendor.js(可能包含 axios 配置和拦截器)
- ❌ 提取 3 个接口就停止(目标: 全量提取)
- ❌ JS 文件在线上,不下载到本地
- ```
+ - 这个接口需要什么参数,参数值能从哪里来?
+ - 这个路由或 meta.auth 是否只在前端控制?
+ - Store 里有没有 token、角色、菜单、权限、用户标识?
+ - sourcemap 是否暴露源码、注释、内部 API 或 key?
+ - 新发现能否喂给 data linkage、JWT、bypass 或 exploit?