跳到正文
← 返回博客
FIELD NOTE / 工程实践

同一个 200 里至少有三种拒绝

HTTP 成功、JSON 外壳和业务数据经常被写成同一次通过。企查查、Boss 直聘和微博把这三种拒绝拆开了。

逆向日志里最容易写错的一行是:“请求已经 200 了。”

它听起来像完成证明,其实只证明传输层把一份响应送回来了。响应可能是登录页 HTML、带错误码的 JSON,也可能是空业务对象。如果这三种结果共用一个成功标记,后续就会改错层:签名被重写、环境被补厚、会话被丢掉,真正挡路的门却没被记账。

《签名正确,为什么 API 仍然不给真实数据?》 已经把算法字节和业务数据拆开。这次要继续拆的是 HTTP 状态本身。

先看三种不同的 200

企查查的签名闸门和登录闸门叠在同一条路径上。错误签名或缺失动态头时,服务端常常直接返回一段登录页 HTML;签名正确但未登录时,同一路径会改成 JSON,业务状态仍是“需要登录”。如果只看 HTTP 200,两次请求都被记成成功。真正有用的差分是响应形态:HTML 还是 JSON,以及 JSON 里的业务码。

Boss 直聘的职位列表更直接。公开 POST 可以发出去,HTTP 也是 200,但业务码进入“环境异常”分支,返回的是种子和时间戳,而不是职位列表。列表数据本来就不在首屏 HTML 里。把首页 200 或接口 200 写成“已经拿到职位”,会把环境保护层误判成页面解析问题。

微博热搜和频道接口也有同类情况。同一组前端路径里,有的返回公开列表,有的返回“无权限”,有的给出登录空态。HTTP 200 只说明访客协议走完了;ok、空数组和权限文案才是业务分支。

这些现场不共享同一套签名算法,却共享同一个记账错误。

拒绝应该记在哪一层

我现在会把一次失败先分到四格,再决定下一步:

观测它能证明什么它不能证明什么
连接失败 / 超时传输或网络瞬态算法、会话或业务权限
HTTP 200 + HTML 外壳走到了某个页面或降级页业务 API 已接受当前请求
HTTP 200 + JSON 错误码过了传输,撞上签名、会话或环境闸算法一定错了
HTTP 200 + 目标字段非空当前会话下这条只读接口给出了数据其他接口、登录态或长期稳定性

企查查让我把“签名闸”和“登录闸”分开。前者改变的是响应是不是 API JSON;后者改变的是 JSON 里有没有业务结果。Boss 直聘让我把“发得出”和“内容拿得到”分开。微博让我把权限空态留在原处,而不是当成解析器缺陷。

B 站 WBI 还有一种更淡的拒绝:HTTP 200,但 body 只剩凭证挑战,没有目标业务字段。它提醒我:公开算法家族被识别出来之后,live 验收仍然要看业务码,而不是签名字符串是否像那么回事。

为什么不能把它们合成一个布尔值

《passed=false 里混着六种结果》 讨论的是验证系统内部的布尔值。Web 协议现场有同样的压缩。

如果“环境异常”“需要登录”“无权限”“签名失败”都写成 failed=true,下一步动作会随机化。签名被重做,可能毁掉已经对齐的算法;环境被补厚,可能走进从未出现过的分支;登录墙被当成风控,可能把合法空态写成未完成逆向。

更稳的做法是让每一种拒绝保留自己的 oracle。签名层用“错签与正签的响应形态差分”;会话层用短期令牌滚动前后的同一请求;环境层用业务码是否进入保护分支;权限层用公开访客与登录态的对照。没有进入那一层的证据,就不改那一层的实现。

这一组现场没有证明什么

它们没有证明整站保护模型,也没有给出可复现的请求配方。公开文章只保留分层判断:200 是传输事实,JSON 外壳是协议事实,目标字段才是业务事实。

Boss 直聘的环境保护模块如何把种子写成后续令牌,当前没有公开运行时闭环。企查查未登录时的业务接口统一停在登录闸,不能由此推出签名无效。微博热榜内容本身会变,验证应落在 schema 和分支语义,而不是某次热词。

所以这篇是现场笔记,不是新的签名教程。它要留下的只有一句:下一次看见 200,先问拒绝写在哪一层。