Files
live-hub-py/docs/HUYA_COOKIE合并审计与来源矩阵.md
T

173 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 虎牙网页 Cookie 合并审计与来源矩阵
更新时间:2026-09-01
## 结论
`core/huya/app_login.py::_merge_web_cookie` 只应合并当前登录流程实际得到的认证值和已有网页 Cookie。Cookie 在抓包中出现,不代表可以凭长度或字符集在本地生成。
9.1 抓包中,活动 WSS 的角色查询和积分查询均成功,说明活动接口当前主要依赖核心认证字段;这不能证明随机设备字段、商城接口和支付链路会接受伪造值。
## 字段来源
| 字段 | 真实来源 | 当前策略 |
| --- | --- | --- |
| `udb_cred` | App WUP 登录响应的 114B `cred`Base64URL 后 152 字符 | 本次值覆盖旧值 |
| `udb_uid``yyuid` | WUP 登录响应解析出的真实 UID | 本次值覆盖旧值 |
| `udb_biztoken` | `tryQrLogin(stage=2)` 响应 | 本次值覆盖旧值 |
| `udb_passport``username` | 网页 Cookie/tryQrLogin/App 登录态中的真实 passport | 只有输入本身是 `hy_` 账号时才写入,否则保留已有值 |
| `udb_version``udb_origin``udb_status` | 网页登录协议固定字段 | 写入协议固定值 |
| `sdid``hdid` | `df/collect``Set-Cookie`/响应 | 本次指纹响应覆盖旧值 |
| `guid` | 网页/launch 会话的 GUID32 | 只接受显式 `device_info["guid"]` 或 Session 值 |
| `udb_guiddata` | 浏览器设备状态字段,和 `guid` 是不同值 | 只接受显式值,不再与 `guid32` 复制 |
| `game_did``__yamid_*`、QIMEI、`udb_anobiztoken` | 浏览器/设备 SDK 状态,部分具有关联或签名 | 无真实值时省略 |
| `udb_deviceid` | 浏览器设备 Cookie | 只接受格式正确且显式提供的值 |
| `Hm_*``HMACCOUNT``*_rep_cnt``_yasids` | 页面统计与访问计数状态 | 只保留已有或显式提供值 |
## 9.1 抓包事实
同一网页会话内这些值保持稳定:
- `_qimei_uuid42`42 位;`_qimei_h38`38 位;`_qimei_fingerprint`:32 位。三者是 QIMEI 设备标识,不能用随机十六进制替代。
- `udb_anobiztoken`344 位 URL-safe 字符串,和 `udb_anouid` 共同属于匿名设备状态,不能用重复 UUID 字节拼接。
- `guid``udb_guiddata` 均为 32 位,但抓包值不同,不能复用同一画像字段。
- `udb_deviceid``w_` 加 19 位数字;格式可以校验,但仅凭格式无法得到真实值。
- `huya_flash_rep_cnt``huyasp_rep_cnt``huya_hd_rep_cnt``huya_web_rep_cnt` 随访问过程变化,不能硬编码抓包历史值。
抓包中还存在 `huya_ua`,它是 WSS/HTTP RPC 的业务 UserId 字段,不属于持久化 Cookie;由 `wss_client.py``http_client.py` 按通道注入。
## 旧实现的问题
旧实现曾在空 Session 中生成 `__yamid_new``game_did`、三个 QIMEI 字段、`udb_anobiztoken``HMACCOUNT``_yasids` 和多个固定计数器。它们只能满足长度测试,不能证明值具有协议语义。
旧实现还把 `device_info["guid32"][:32]` 同时写入 `guid``udb_guiddata`,并把手机号直接写入 `udb_passport`/`username`。这两点会造成设备身份和账号映射偏差。
## 验证要求
1. 登录后必须检查 `udb_cred``udb_biztoken`、UID、`sdid``hdid` 的来源和值覆盖关系。
2. 活动 WSS、商城 WSS、下单和支付分别验证,不能用角色查询成功替代全流程验证。
3. 新增 Cookie 字段前,必须有同一会话的响应、页面状态或设备 SDK 输出作为来源证据;只有长度/正则证据时保持省略。
### 9.1 抓包新增证据
`705-meta.json``GET www.huya.com/30596253` 在请求发出时已经携带 43 个网页 Cookie,
包括 `guid``game_did`、三个 QIMEI、`udb_guiddata``udb_deviceid``udb_anobiztoken`
和统计字段;该响应没有 `Set-Cookie`。随后 `714-meta.json`
`POST df.huya.com/web/df/collect` 只返回 `sdid``hdid` 两个 `Set-Cookie`
因此 App 的 `/web/cookie/verify` 不能兑换出完整网页 CK。网页设备字段来自浏览器此前
持久化的 Cookie/Storage 和页面脚本运行态;当前 Python App 登录链不应声称能凭空补齐它们,
也不在脚本中外挂浏览器。2026-09-01 起接入匿名设备态协议
`core/huya/anon_device.py`middle + anonymousLogin)补 `udb_deviceid`/
`udb_guiddata`/`udb_anobiztoken`/`udb_anouid` 四个字段;其余脚本字段(`guid`/
`game_did`/`_qimei_uuid42`/`__yamid_new`)缺失时不再拒绝登录,改为警告并保存
(风控风险自担);精英宝典任务不再回退 HTTP。
### 运行时复核(2026-09-01
当前 Node 指纹运行器已接入 `__COOKIES__` 输出桥:只有 hydevice 通过
`document.cookie` 实际写入的值才会进入 `HuyaSdidResult.cookies`,并在合并阶段按
显式值转发。对当前内置 `hydevice-prod-1.2.45-min.js` 的实际运行结果为:
- `__SDID__``__HDID__` 正常返回;
- Cookie 桥只观察到脚本自身的 `udb_appid`/临时 `sdid`,没有生成 `guid`、QIMEI、
`udb_guiddata``udb_deviceid``udb_anobiztoken`
- 因此这次改动此前不会改变 `COOKIE_INCOMPLETE` 判定,也不会用随机值补齐字段;
2026-09-01 登录链接入匿名设备态协议后可补齐 4 个服务端签发字段(见下节)。
从线上下载的 `hydevice-prod-1.2.51-min.js` 在现有 Node 仿真环境中还依赖未实现的
浏览器 API(首个错误为 `window[t(...)] is not a function`),没有将其替换进生产链。
后续若升级仿真环境,必须先观察到真实 Cookie 写入,再纳入合并逻辑。
### 多抓包交叉核验
- `8.31/reqable完整抓包-1.har`:首次页面请求和 QR 请求均已携带全部 7 个字段;
QR 成功响应只新增 `udb_cred``udb_uid``yyuid``udb_passport``username`
`udb_version``udb_biztoken``udb_origin``udb_status`
- `8.31/cahrles完整抓包-1.har`:第一个带网页设备态的请求早于 `df/collect` 和 QR
成功响应;`df/collect` 只下发 `sdid``hdid`
- `7.5-短信登录-滑块.har``web/cookie/verify` 前后的请求也没有新增这 7 个字段。
因此当前日志中的缺失列表是协议事实,不是 Cookie 合并阶段丢失;服务端正式签发
接口已找到并接入登录链(middle + anonymousLogin,见“匿名流程解密”节)。
### 真实浏览器实测(2026-09-01,未登录)
用真实 Chrome(有头)通过 CDP 抓包复核,两轮验证:
`core/huya/browser_harvest.py`round1 全新 profile / round2 同 profile 重启),
原始数据与报告在 `evidence/browser_cookie_harvest/`
页面序列:`www.huya.com` 门户 → 直播间 `30596253`,全程不登录。
| 字段 | 首次出现位置 | 产生机制 | round2 重启后首请求携带 |
| --- | --- | --- | --- |
| `__yamid_new` | `liveapi.huya.com/ip/getIpLocation` (XHR) | 门户页脚本写 `document.cookie` | ✅ |
| `game_did` | 同上 | 脚本写入 | ✅ |
| `udb_guiddata` | `udblgn.huya.com/web/middle/2.4/...` (Document) | UDB SDK 脚本写入,与 `guid` 值不同 | ✅ |
| `guid` | `udbres.huya.com/js/HyUDBWebSDK-Exchange-2.4.js` | UDB SDK 脚本写入 | ✅ |
| `_qimei_uuid42` | 同上 | QIMEI SDK 脚本写入 | ✅ |
| `udb_deviceid` | `udblgn.huya.com/web/middle/2.4/...``/web/anonymousLogin``Set-Cookie` | 服务端匿名接口下发,`w_`+19 位数字,10 年 | ✅ |
| `udb_anobiztoken` | `udblog.huya.com/web/log/report` | UDB SDK 脚本写入 | ✅ |
结论:
1. 7 个必填字段全部由**浏览器页面运行态**产生(6 个脚本写 `document.cookie`1 个
`udb_deviceid` 由匿名服务端接口下发),与登录协议无关 —— `COOKIE_INCOMPLETE`
判定是协议事实。
2. round2 重启后门户首 Document 请求直接携带 16 个 Cookie(7 个必填字段全部在内),
证明这些字段是**浏览器持久态**,跨会话稳定复用。
3. 首次访问时序:门户首请求无 Cookie → 脚本热启动后 `__yamid_new`/`game_did`
先出现 → UDB SDK 加载后 `guid`/`udb_guiddata`/QIMEI 出现 → `anonymousLogin`
匿名 XHR 携全套字段并把 `udb_deviceid` 种回 10 年。
4. 全会话服务端 `Set-Cookie``PHPSESSID``udb_deviceid``sdid``HMACCOUNT`
(百度统计)、mediav `v` 五类,其余全部来自脚本。
新突破口:`udblgn.huya.com/web/anonymousLogin``/web/middle/2.4/...` 是公开
匿名接口(纯 HTTP 可调),服务端正式签发 `udb_deviceid` —— 这是“服务端正式签发
接口”的第一个实例;`guid`/QIMEI/`udb_anobiztoken` 仍无服务端下发,只能靠复刻
页面 SDK`HyUDBWebSDK-Exchange-2.4.js` 等)的脚本语义。
### 匿名流程解密(2026-09-01,纯 HTTP 复放验证)
浏览器实际匿名链(未登录):
1. `GET udblgn.huya.com/web/middle/2.4/<8位随机>/https/<32hex>`
`Set-Cookie: udb_deviceid=w_<19位>; Max-Age=315360000; Domain=huya.com`
响应是一个加载 `HyUDBWebSDK-Exchange-2.4.js``init()` 的引导 HTML。
2. SDK 随后发 `POST udblgn.huya.com/web/anonymousLogin`
`Content-Type: application/json``Referer` 必须为 middle URL
否则返回 `error!`):
- 请求体 `{"uri":"10013","version":"2.4","context":"WB-<hex32>-<hex32>-<hex32>",
"appId":5002,"sdid":"csid_<hex32>","lcid":2052,"byPass":3,
"requestId":<8位>,"authId":"","data":{"domainList":""}}`
- 响应 `returnCode:0``data.uid`12 位匿名 uid)、`data.biztoken`344 位
URL-safe)、`Set-Cookie: udb_anouid`7 天)+ `udb_deviceid`。
3. 页面 SDK 把 `biztoken` 写入 `udb_anobiztoken`、`uid` 写入 `udb_anouid`
与 middle URL 的 32hex= `udb_guiddata`)、`guid`、QIMEI、`__yamid_new`、
`game_did` 一起构成网页设备态。
对照验证(真实浏览器最终 Cookie 与响应值一一对应):
| Cookie | 值形态 | 来源 |
| --- | --- | --- |
| `udb_deviceid` | `w_`+19 位数字 | middle/anonymousLogin `Set-Cookie`10 年) |
| `udb_anobiztoken` | 344 位,`AQA…` 开头 | anonymousLogin 响应 `data.biztoken` |
| `udb_anouid` | 12 位数字 | anonymousLogin 响应 `data.uid`/`Set-Cookie`7 天) |
| `udb_guiddata` | 32hex | middle URL 的第三段参数(客户端自选) |
| `guid` | 32hex | 页面 SDK 脚本写入 `document.cookie` |
| `_qimei_uuid42` | 42 位 | QIMEI SDK 脚本写入 |
| `__yamid_new` | 32hex | 门户业务脚本写入 |
| `game_did` | 35 位 | 门户业务脚本写入 |
复放实验结论(2026-09-01):
- middle 的 8 位随机数与 32hex 均**不校验**:任意随机值 GET 即签发新的
`udb_deviceid``returnCode 200`);
- anonymousLogin 的 `context`/`csid`/`requestId` 均为全新随机值时,无任何
Cookie 直接 POST 也返回 `returnCode:0` + 新 `biztoken` 与 `Set-Cookie`
- 因此 **`udb_deviceid`、`udb_anobiztoken`、`udb_anouid`、`udb_guiddata`
四个字段可由纯协议生成**,不再依赖真实浏览器脚本;
- 剩余 `guid`、`_qimei_uuid42`、`__yamid_new`、`game_did` 没有服务端签发,
服务端是否校验其“真实性/关联性”未知 —— `200` 只代表接口接受,不代表
风控认可。必须用协议生成的设备态跑通线上 活动 WSS/商城/支付 全链路
才能作为最终结论,不能只凭长度测试(与本文开头结论一致)。