Files

382 lines
22 KiB
Markdown
Raw Permalink 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.

# 最终请求验证与 TLS 指纹兼容
当新 case 需要最终发送真实请求、交付 `final.js` / `final.py`,或用户提到 CycleTLS、impers、curl-cffi-node、curl_cffi、cffi_curl、cyCronet 时读取本文件。TLS 指纹兼容客户端的选择应从前置阶段开始确认,不要等普通 `fetch` / `requests` 失败后才临时切换;最终请求一律使用 Session 模式。
## 使用边界
- 只用于用户授权范围内的网页端 JS 补环境结果验证。
- 只做低频、最小化请求,优先复现用户已提供的成功 cURL / HAR。
- TLS 指纹兼容只解决普通 HTTP 客户端与浏览器在 TLS ClientHello、ALPN、HTTP/2、JA3/JA4、Cronet / curl-impersonate 网络栈上的差异;不能替代登录态、验证码、一次性 token、设备校验或业务授权。
- 最终真实请求必须写入一体化 `final.js` 或 `final.py`,由 Node.js / Python TLS 指纹兼容 Session 客户端直接发起;不得生成加密参数后再使用 ruyiPage、Playwright、Puppeteer、Selenium、CloakBrowser 或其他浏览器自动化验证。
- 如果目标 JS 运行时调用 `XMLHttpRequest` / `fetch` / `sendBeacon` 访问动态资源、Cookie、challenge、telemetry 或目标接口,JS 侧网络 API 也必须通过同一 TLS 指纹兼容 Session 的 live session bridge 发送;fixture/mock/default 200 只能作为离线诊断,不能作为 TLS 验证。
## 前置阶段必须选择客户端
在信息完整性检查和任务确认时,加入以下字段:
```markdown
- 最终请求 TLS 指纹兼容客户端:Node.js CycleTLS / Node.js impers / Node.js curl-cffi / curl-cffi-node / Python curl_cffi / Python cffi_curl / Python cyCronet / 不发真实请求
- 最终请求 Session 模式:一律启用 / 不发真实请求
- 是否已安装:是 / 否 / 待检测
- 若未安装:安装该客户端 / 改选其他客户端 / 不发真实请求
```
选择后立即检测:
```bash
node scripts/check_tls_clients.js --markdown
node scripts/check_tls_clients.js --python python --markdown
```
如果用户选择的库未安装,不要默认退回普通 `fetch` / `requests` 发真实请求。应让用户确认安装、改选其他已安装客户端,或选择“不发真实请求,只输出本地 sign / 参数”。
## 工具选择
| 运行环境 | 可选工具 | 适用场景 |
|---|---|---|
| Node.js | CycleTLS / cycletls / @luminati-io/cycletls | 需要在 Node.js `final.js` 中指定 JA3 / User-Agent / Header 并发起少量验证请求 |
| Node.js | impers | Node.js curl-impersonate 路线;需要浏览器 TLS / JA3 / HTTP2/HTTP3 指纹时优先考虑 |
| Node.js | curl-cffi-node / curl-cffi | Node.js 侧 curl_cffi / curl-impersonate 路线;需要用 Node.js 维持 session 与 Cookie jar 时可选 |
| Python | curl_cffi / cffi_curl | Python curl-impersonate 路线;常用于 Chrome / Firefox / Safari impersonate |
| Python | cyCronet / cycronet | Python Cronet-Cloak / Chromium 网络栈路线;适合需要 Chrome 系网络栈特征时 |
| 不发真实请求 | 无 | 只交付本地参数生成结果,不进行最终接口验证 |
注意:不同机器安装包名、导入名、版本和 API 可能不同。先运行检测脚本,不要在 Skill 中硬编码本机路径或版本。库 API 变动时,以本机安装版本的 README / 官方文档为准。
## 安装提示
只在用户确认后安装;不要在未确认时自动安装依赖。
```bash
# Node.js
npm install cycletls
npm install @luminati-io/cycletls
npm install impers
npm install curl-cffi
# Python
python -m pip install curl_cffi
python -m pip install cffi_curl
python -m pip install cycronet
```
## 高强度网络指纹一致性
## 用户 cURL 与取证 baseline 的网络指纹优先级
用户提供的 cURL / HAR 不一定来自最终取证浏览器。最终请求代码必须优先对齐“已确认取证 baseline”,而不是无条件复用用户 cURL 中的 UA、Client Hints、Header、Cookie 或 TLS 线索。
硬性规则:
- 前置阶段必须识别用户 cURL 样本浏览器族和取证 baseline 浏览器族。
- 如果两者一致,仍需确认 Header、TLS / HTTP2、Cookie jar 和代理 / 地区是否属于同一会话链路。
- 如果两者不一致,默认以取证 baseline 为准;cURL 只作为请求结构、参数位置、业务字段和历史现象线索。
- Chrome cURL 与 Firefox 取证冲突时,不得在 Firefox baseline 的最终请求中保留 Chrome `sec-ch-ua`、`sec-ch-ua-platform`、`sec-ch-ua-mobile`,也不得只修改 UA 后继续使用 Chrome TLS / HTTP2 profile。
- 最终 `final.js` / `final.py` 的 UA、Accept-Language、Client Hints、Header 顺序、TLS / JA3 / JA4、HTTP/2 Akamai 指纹、Cookie jar 和代理必须来自同一取证 baseline 或经过重新取证确认。
- cURL 中已有的 sign、token、Cookie challenge 值只能作为 fixture / 历史线索;最终项目必须通过补环境后的 signer 和同一 session 请求链生成或刷新。
- 冲突记录必须写入 `case/notes/sample-baseline-conflict.md`、`case/notes/final-request-validation.md` 和最终总结。
- 如果用户坚持沿用 cURL 浏览器族,则暂停最终请求验证,要求使用同浏览器族取证工具重新采样 baseline,或让用户明确接受风险;风险确认不得替代成功验证。
高强度检测中,TLS 指纹兼容不是单独开关,而是要与浏览器取证 baseline 一起看。最终请求前必须核对:
- TLS / JA3 / JA4、ALPN、HTTP/2 / HTTP/3 能力与所选 impersonate 浏览器族一致。
- `User-Agent` 与 `navigator.userAgent` 一致。
- `sec-ch-ua`、`sec-ch-ua-platform`、`sec-ch-ua-mobile` 与 `navigator.userAgentData` 一致;取证浏览器是 Firefox 时不要伪造 Chrome Client Hints。
- `Accept-Language` 与 `navigator.language/languages` 一致。
- `Accept-Encoding`、Header 顺序、HTTP/2 pseudo-header 顺序、`sec-fetch-*`、`Referer`、`Origin` 与 HAR / 浏览器请求链一致。
- 代理 / IP / 地区、timezone、locale、WebRTC 策略与 fingerprint baseline 一致。
- Cookie jar 和 Storage 状态来自同一 session 链路,不复制旧 cURL 中已过期的风控状态。
如果以上任一项冲突,先修正请求链和 fixture,不要把失败直接归因于 JS 补环境。
## Firefox baseline 与 curl_cffi TLS 指纹对齐
当取证浏览器 baseline 是 Firefox(例如 ruyiPage 定制 Firefox、用户手动 Firefox 或其他 Firefox 系取证工具),而最终请求客户端选择 Python `curl_cffi` / Node.js `curl-cffi-node` 时,不得仅凭 `impersonate="firefox147"`、`impersonate="firefox"` 或修改 `User-Agent` 就宣称与取证浏览器一致。Firefox 版本标签只是 curl_cffi 支持的预设 profile 名称,真实是否兼容必须以当前 case 的浏览器 TLS / HTTP2 采样证据为准。
### 硬性流程
1. 先按 `fingerprint-baseline-consistency.md` 固化当前 case 的 Firefox 指纹基线,记录 `baselineId`、取证工具、profile / seed、代理、语言、时区、UA、Header 和浏览器路径摘要。
2. 使用已确认的取证工具在同一 baseline 下访问 TLS 指纹检测端点,建议至少采样:
- `https://tls.peet.ws/api/all`
- `https://tls.browserleaks.com/json`
3. 记录真实 Firefox baseline 的 `ja3`、`ja3_hash`、`ja3n_hash`、`ja4`、cipher suites、extension 顺序、supported groups / curves、signature algorithms、delegated credentials、record size limit、key_share、ALPN、HTTP/2 Akamai fingerprint、UA 和请求头。
4. 再用 curl_cffi 的候选 profile(例如 `firefox147` 或当前 `firefox` alias)访问同一端点并对比。
5. 如果裸 profile 与 Firefox baseline 不一致,不允许只改 UA 后继续;必须尝试通过 `ja3`、`akamai`、`extra_fp`、`curl_options` 和 HTTP Header 对齐。
6. 对齐后再次访问检测端点,只有 `ja3_hash`、`ja3n_hash`、`ja4`、HTTP/2 Akamai fingerprint、关键 TLS 字段和 UA / Header 与 baseline 一致或经过用户确认可接受等价时,才允许进入最终请求验证。
7. 对齐配置必须写入 `case/notes/final-request-validation.md` 和最终总结;如果仍不一致,必须暂停、换客户端或记录用户明确接受的风险,不得伪造成功。
### 必须对比的字段
- TLS:`ja3`、`ja3_hash`、`ja3n_hash`、`ja4`、TLS version、cipher suites 列表和顺序、TLS extensions 列表和顺序、supported groups / curves、signature algorithms、delegated credentials、record size limit、key_share 数量和顺序、ALPN、ECH / certificate compression 等扩展摘要。
- HTTP/2:Akamai fingerprint、settings、window update、streams、pseudo-header 顺序。
- HTTP Header:`User-Agent`、`Accept`、`Accept-Language`、`Accept-Encoding`、`Referer`、`Origin`、`Sec-Fetch-*`、Header 顺序和大小写。
- Session 与环境:Cookie jar、代理 / IP、timezone、locale、`navigator.userAgent`、`navigator.language/languages`、Firefox 不应伪造 Chrome UA-CH。
### curl_cffi Firefox 对齐模板
以下模板用于“Firefox baseline 已采样,且确认 curl_cffi 裸 profile 不一致”时的对齐。示例中的具体值来自某次 ruyiPage Firefox 151 baseline 验证,只能作为写法参考;真实 case 必须把 `FIREFOX_BASELINE_JA3`、`FIREFOX_BASELINE_AKAMAI`、`headers`、`extra_fp` 和 `curl_options` 替换为当前 case 采样结果。
```python
from curl_cffi import requests
from curl_cffi.const import CurlOpt
FIREFOX_BASELINE_JA3 = (
"771,4865-4867-4866-49195-49199-52393-52392-49196-49200-"
"49171-49172-156-157-47-53,"
"0-23-65281-10-11-35-16-5-34-18-51-43-13-45-28-27-65037,"
"4588-29-23-24-25-256-257,0"
)
FIREFOX_BASELINE_AKAMAI = "1:65536;2:0;4:131072;5:16384|12517377|0|m,p,a,s"
FIREFOX_HEADERS = {
# 中文说明:UA 必须来自当前 case 的 Firefox baseline,不要沿用 curl_cffi 默认 UA。
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:151.0) Gecko/20100101 Firefox/151.0",
# 中文说明:Accept / Accept-Language / Accept-Encoding 等也要以 HAR 或浏览器样本为准。
}
FIREFOX_EXTRA_FP = {
# 中文说明:signature_algorithms 必须按真实 Firefox baseline 的顺序填写。
"tls_signature_algorithms": [
"ecdsa_secp256r1_sha256",
"ecdsa_secp384r1_sha384",
"ecdsa_secp521r1_sha512",
"rsa_pss_rsae_sha256",
"rsa_pss_rsae_sha384",
"rsa_pss_rsae_sha512",
"rsa_pkcs1_sha256",
"rsa_pkcs1_sha384",
"rsa_pkcs1_sha512",
"rsa_pkcs1_sha1",
],
# 中文说明:delegated credentials 使用冒号分隔;不要把逗号分隔误写为最终配置。
"tls_delegated_credential": "ecdsa_secp256r1_sha256:ecdsa_secp384r1_sha384:ecdsa_secp521r1_sha512",
# 中文说明:record_size_limit 需来自真实浏览器样本,例如 Firefox 151 样本为 0x4001。
"tls_record_size_limit": 0x4001,
}
def create_firefox_aligned_curl_cffi_session():
# 中文说明:最终项目必须使用 Session;动态资源刷新、Cookie 生成和目标 API 都复用同一 Session。
# 中文说明:ja3 / akamai / extra_fp / curl_options 在 Session 初始化时固定,避免单次请求遗漏。
session = requests.Session(
impersonate="firefox147",
ja3=FIREFOX_BASELINE_JA3,
akamai=FIREFOX_BASELINE_AKAMAI,
extra_fp=FIREFOX_EXTRA_FP,
curl_options={
# 中文说明:用于对齐 Firefox key_share 数量;实际值必须通过采样验证。
CurlOpt.TLS_KEY_SHARES_LIMIT: 2,
},
)
session.headers.update(FIREFOX_HEADERS)
return session
def send_with_firefox_aligned_tls(session, method, url, **kwargs):
headers = dict(FIREFOX_HEADERS)
headers.update(kwargs.pop("headers", {}) or {})
return session.request(
method=method,
url=url,
headers=headers,
**kwargs,
)
```
### 对齐验收示例
对齐成功后,检测端点应至少达到以下级别:
```text
ja3_hash: 与 Firefox baseline 一致
ja3n_hash: 与 Firefox baseline 一致
ja4: 与 Firefox baseline 一致
akamai_hash / akamai_text: 与 Firefox baseline 一致
HTTP User-Agent: 与 navigator.userAgent / HAR 一致
HTTP/2: settings、window update、pseudo-header 顺序一致
```
若只修改 UA 后 `ja3_hash`、`ja4`、Akamai HTTP/2 指纹仍不一致,视为未对齐,不得进入最终请求验证。
### cyCronet / cycronet 的边界
`cyCronet` / `cycronet` 更适合 Chromium / Chrome baseline。即使当前版本提供 `add_tls_profile` / `set_tls_profiles`,通常也主要控制 cipher suites、curves 和部分 extensions,底层仍是 Cronet / Chromium 网络栈,HTTP/2 Akamai 指纹、JA4 结构和 Firefox 的 BoringSSL / NSS 行为可能不同。
- 取证 baseline 是 Firefox:优先使用 curl_cffi 的 Firefox 对齐流程;不要把 cyCronet 作为 Firefox 对齐首选。
- 取证 baseline 是 Chrome / Chromium / Camoufox Chromium 类工具:可以考虑 cyCronet,并同样先采样 baseline、再对比 JA3 / JA4 / HTTP2 指纹。
- 如果用户明确要求用 cyCronet 对齐 Firefox,必须先实测;若 JA4 或 HTTP/2 指纹无法一致,暂停并说明能力边界。
## Session 模式硬规则
最终请求不再区分单请求或请求链。只要用户选择发送真实请求或交付可请求的 `final.js` / `final.py`,必须读取 `session-request-chain.md` 并满足:
1. 创建 session client,不使用无状态单次请求。
2. 动态资源刷新、Cookie / challenge 生成、加密参数生成前后请求和目标 API 都走同一 session。
3. 同一 session 复用 Cookie jar、UA、Client Hints、Accept-Language、Referer、Origin、Header 顺序、代理、TLS 指纹和 fingerprint baseline。
4. 成功、失败或异常退出后,在 `finally` 中调用 `close()` / `exit()` / `dispose()`,并清理 Cookie jar、敏感 header、token 和临时响应。
5. 最终总结记录 session client 类型、请求链、Cookie jar 来源、销毁方式和敏感状态清理结果。
## Node.js CycleTLS 模板
CycleTLS 的包名和导出在不同版本中可能不同,先用 `check_tls_clients.js` 确认。最终项目中建议封装成 `result/src/request/client.js`,只由 `result/final.js` 调用。
```javascript
'use strict';
async function createCycleTLSSession({ ja3, userAgent, headers = {} }) {
const initCycleTLS = require('cycletls');
const cycleTLS = await initCycleTLS();
const cookieJar = new Map();
async function request({ url, method = 'GET', headers: extraHeaders = {}, body }) {
const response = await cycleTLS(url, {
method,
headers: { ...headers, ...extraHeaders },
body,
ja3,
userAgent,
}, method.toUpperCase());
// 中文说明:这里按项目需要解析 Set-Cookie 并写回 cookieJar,后续请求复用。
return { status: response.status, headers: response.headers, body: response.body };
}
async function close() {
cookieJar.clear();
if (typeof cycleTLS.exit === 'function') cycleTLS.exit();
}
return { request, close, cookieJar };
}
module.exports = { createCycleTLSSession };
```
## Node.js impers 模板
`impers` 是 Node.js curl-impersonate 路线,官方说明它处于 alpha 状态,API 可能变化。使用时优先按本机安装版本确认导出。常见用法是指定 `impersonate`,例如 `chrome`。
```javascript
// ESM 示例;若最终项目使用 CommonJS,可把请求客户端单独写成 .mjs 或动态 import。
import * as impers from 'impers';
export async function sendRequestWithImpers({ url, method = 'GET', headers = {}, body, impersonate = 'chrome' }) {
const fn = method.toLowerCase();
if (typeof impers[fn] === 'function' && !body) {
return await impers[fn](url, { headers, impersonate });
}
return await impers.request({ url, method, headers, body, impersonate });
}
```
## Python curl_cffi 模板
普通 Chrome / Safari / 已有预设 profile 可使用下列基础模板;如果取证 baseline 是 Firefox 且 curl_cffi 预设 profile 与真实浏览器 TLS / HTTP2 指纹不一致,必须优先按上文“Firefox baseline 与 curl_cffi TLS 指纹对齐”生成专用 Session 封装,不得只使用基础模板或只改 UA。
```python
from curl_cffi import requests
def create_curl_cffi_session(headers=None, impersonate="chrome"):
session = requests.Session(impersonate=impersonate)
session.headers.update(headers or {})
return session
def send_request_with_curl_cffi(session, url, method="GET", data=None, json_data=None, headers=None):
resp = session.request(
method=method,
url=url,
headers=headers or {},
data=data,
json=json_data,
timeout=30,
)
return {"status": resp.status_code, "headers": dict(resp.headers), "text": resp.text}
def close_curl_cffi_session(session):
try:
session.cookies.clear()
finally:
session.close()
```
## Python cffi_curl / cyCronet 模板
`cffi_curl` 与 `cyCronet` / `cycronet` 的 API 版本差异较大,Skill 不应硬编码某个本机版本。`cyCronet` / `cycronet` 默认优先用于 Chrome / Chromium baseline;Firefox baseline 应优先走 curl_cffi 对齐流程,除非已实测 cyCronet 的 JA3 / JA4 / HTTP2 与 Firefox baseline 一致。使用方式:
1. 先运行 `check_tls_clients.js --python python --json` 确认导入名。
2. 在 `result/src/request_client.py` 封装一个统一函数 `send_request(...)`。
3. 只在封装内部处理库差异;`final.py` 不直接散落库调用。
4. 若当前包 API 与模板不一致,以本机包文档为准,并在 `notes/final-request-validation.md` 记录使用版本和参数。
伪代码:
```python
def send_request(url, method="GET", headers=None, body=None):
# 这里根据用户已确认且本机已安装的 cffi_curl / cyCronet API 实现。
# 必须返回 status、headers、text/body。
raise NotImplementedError("请根据本机 cffi_curl / cyCronet 版本补齐请求客户端封装")
```
## 最终验证流程
1. 前置阶段确认最终请求客户端和是否已安装。
2. 确认 fixtures 已通过,Node.js 生成参数与浏览器样本一致。
3. 若目标 JS 依赖 XHR/fetch 网络访问,先按 `xhr-fetch-session-bridge.md` 建立 live session bridge,并确认 Cookie jar / Set-Cookie / resource timing 同步。
4. 读取用户提供的成功 cURL / HAR,不凭空构造请求。
5. 脱敏保存请求样本;真实 Cookie / token 只保存在本地,不写入最终报告。
6. 使用已确认的 CycleTLS / impers / curl-cffi-node / curl_cffi / cffi_curl / cyCronet 创建 session client,并在同一 session 中仅发起少量验证请求;若用户选择“不发真实请求”,则只输出本地 sign / 参数。
7. 对比:
- HTTP 状态码。
- 响应 JSON 中关键字段。
- 服务端是否接受新生成的加密参数。
- 是否出现风控 / 验证码 / 登录失效。
8. 写入 `notes/final-request-validation.md`。
9. 在 `finally` 中销毁 session,清理 Cookie jar、临时响应、日志和敏感请求副本。
10. 将最终请求逻辑整合进 `result/final.js` 或 `result/final.py`,并运行 `check_xhr_fetch_session_bridge.js` 与 `check_final_artifact.js` 确认不包含浏览器自动化代码和多余产物。
## 输出模板
```markdown
## 最终请求验证
- 是否执行真实请求:是 / 否
- 用户授权:是 / 否
- 前置阶段已选客户端:Node.js CycleTLS / Node.js impers / Node.js curl-cffi / curl-cffi-node / Python curl_cffi / Python cffi_curl / Python cyCronet / 不发真实请求
- 客户端安装状态:已安装 / 未安装改选 / 未安装不发请求
- Session 模式:已创建 session / 不发真实请求
- 请求链是否复用同一 session:是 / 否,原因:
- XHR/fetch Session Bridge:未涉及 / offline-fixture / live-session-bridge;如使用 Python curl_cffi,是否由 final.py 持有同一 session 并服务 Node IPC:
- Session 销毁方式:close / exit / dispose / Cookie jar 清理 / 不适用
- 最终项目入口:final.js / final.py
- 是否包含浏览器自动化代码:否
- 请求来源:cURL / HAR / 用户样本
- TLS 指纹兼容原因:用户前置选择 / 目标接口要求浏览器网络栈一致 / 未启用
- Firefox / curl_cffi 对齐状态:不涉及 / 已对齐 / 未对齐改选 / 用户确认风险
- TLS baseline 来源:ruyiPage / Camoufox / CloakBrowser / 用户手动浏览器 / HAR;baselineId:
- 用户 cURL 浏览器族与取证 baseline 浏览器族:一致 / 冲突 / 未知;冲突处理:以取证 baseline 为准 / 重新取证 / 切换工具 / 用户确认风险
- 是否阻止沿用冲突 cURL 字段:UA / Client Hints / Header / TLS profile / HTTP2 指纹 / Cookie / 动态参数
- 对齐参数摘要:ja3 hash / ja4 / akamai hash / extra_fp / curl_options / Header 是否与 baseline 一致
- 请求次数:
- 状态码:
- 目标加密参数是否被接受:
- 响应关键字段:
- 是否触发验证码 / 风控:
- 敏感材料处理:已脱敏 / 仅本地保存 / 已清理
```
## 排查提示
如果 TLS 指纹兼容客户端仍失败,优先排查:
- Cookie / Authorization 是否过期。
- Header 顺序、大小写、HTTP/2 伪头、Content-Length 是否与样本一致。
- Body 是否保持原始字符串,未被 JSON 重新序列化。
- Query 编码、排序、空值、数组序列化是否一致。
- 时间戳、nonce、随机数、server seed 是否过期。
- IP / 代理 / 地域 / 账号风控是否变化。
- 目标接口是否绑定浏览器会话、Service Worker、一次性 token 或挑战结果。