# Feature 验证与记录指南

> 由主 agent 在阶段 2 读取。与 `r/023.txt` 共同构成 Attack 阶段核心流程。

---

## Feature 流水线（后半）

### 步骤 3：验证——深度优先，不只看状态码

**验证的目标是确认"业务影响"，不是确认"接口返回 200"。**

#### 3a. 基础 PoC

设计最小 PoC，发送请求，观察响应。

#### 3b. 影响验证（关键，经常被跳过）

每个"可能成功"的 PoC 之后，**必须验证业务状态真的改变了**，举例：

| 漏洞类型         | 影响验证方式                                                                           |
| ---------------- | -------------------------------------------------------------------------------------- |
| 越权读           | 响应体中确实包含对方的私有数据（不只是 200，要看内容）                                 |
| 越权写           | 查询对应资源确认数值/状态已改变                                                        |
| 交易篡改（金额） | 查余额/账单，确认数据库层面的金额确实变了                                              |
| 竞态             | 查最终状态，确认是否被多次处理                                                         |
| 状态跳转         | 查订单/资源状态，确认绕过了哪个中间步骤                                                |
| XSS              | 确认 payload 出现在响应 HTML 的什么位置（innerHTML vs 转义）                           |
| SQL 注入         | 时间盲注用多组 SLEEP(0)/SLEEP(2)/SLEEP(5) 对比，布尔注入用 AND 1=1 vs AND 1=2 对比结果 |

#### 3c. 参数空间扩展

对每个写入型接口，除了测试 JS 里显式传递的参数，还要测试：

- **数据库可能存在但 JS 未暴露的字段**（如 `role`、`status`、`is_admin`、`merchant_id`）
- **时间类隐藏字段**：`created_at`、`order_time`、`pay_time`、`timestamp`、`expire_at`、`start_time`、`end_time`——服务端若信任客户端传入的时间，可伪造历史记录或绕过时间窗限制（如折扣有效期）
- **金额/价格类**：`price`、`discount`、`fee`、`point_price`、`original_price`——即使 JS 里没有传这些字段，也值得尝试注入
- **回调/跳转类**：`sign`、`callback_url`、`notify_url`、`return_url`、`redirect`——服务端若透传这些值则可能产生 SSRF 或 URL 跳转
- **边界值**：负数、0、极大值、小数精度、空字符串、null
- **类型混淆**：整数字段传字符串、字符串字段传数组/对象
- **格式变体**：`price=1.00` 和 `price=1` 和 `price=1e0` 服务端处理是否一致

#### 3d. 状态机遍历

对涉及状态转换的 feature（订单、支付、审核、库存），系统测试：

```
1. 能否跳过前置步骤直达终态？（未付款→直接退款、未审核→直接操作）
2. 能否从终态逆向操作？（已完成→重新退款、已拒绝→重新审核）
3. 并发执行同一状态转换，最终状态是否正确？
4. 回调/通知接口是否独立于主流程，可以单独调用？
```

#### 3e. 竞态测试

对涉及计数/扣减/领取的操作，用**真正的并发**测试：

```python
# 用 Python asyncio，比 bash & 更可靠
import asyncio, aiohttp

async def race(session, url, data, cookie_header):
    async with session.post(url, json=data, headers={'Cookie': cookie_header}) as r:
        return await r.json(content_type=None)

async def main(url, data, cookie, n=30):
    async with aiohttp.ClientSession() as s:
        results = await asyncio.gather(*[race(s, url, data, cookie) for _ in range(n)])
    ok = [r for r in results if isinstance(r, dict) and r.get('success')]
    print(f"{len(ok)}/{n} succeeded")
    return ok

asyncio.run(main(URL, DATA, COOKIE))
```

30 个真并发比 10 个 bash 后台进程有效得多，能发现时间窗口很窄的竞态。

### 步骤 4：记录

状态优先级：

- `confirmed`：有完整证据链（请求+响应+业务影响验证）
- `suspicious`：响应异常但未验证业务影响
- `not_vulnerable`：测过多个合理变体均无效
- `blocked`：环境限制无法测试

**不要急于标 not_vulnerable。** 一个 PoC 失败不代表漏洞不存在，可能只是参数名/值/格式不对。穷举合理变体后再降级。

**反早闭硬规矩:任何 `not_vulnerable` 必须附 `unruled_out`。** 标"安全"前,先列出"针对这个验证机制/这个输入,我**还没排除**的攻击面",逐条要么补测、要么给排除理由。不确定 ≠ not_vulnerable——不确定就归 suspicious 或继续测。这条对应 G2,专治"测浅一种姿势就判安全"这个最常见的失败模式。

举例(你自己列,不查表):captcha 排除了"省略/空/复用"吗?sms_code/OTP 排除了"类型混淆/暴破"吗?SQLi 只试了报错,排除了"布尔/时间盲注"吗?用户枚举只看了一处报文,排除了"timing / register+forgot 差异响应"吗?每个登录端点(user/merchant/admin)的暴破/锁定,是分别测的,还是用一个的结论类推的?

`unruled_out` 是钩子不是终点——把它列出来,你自己就会去补。
