Files

22 KiB
Raw Permalink Blame History

最终请求验证与 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 验证。

前置阶段必须选择客户端

在信息完整性检查和任务确认时,加入以下字段:

- 最终请求 TLS 指纹兼容客户端:Node.js CycleTLS / Node.js impers / Node.js curl-cffi / curl-cffi-node / Python curl_cffi / Python cffi_curl / Python cyCronet / 不发真实请求
- 最终请求 Session 模式:一律启用 / 不发真实请求
- 是否已安装:是 / 否 / 待检测
- 若未安装:安装该客户端 / 改选其他客户端 / 不发真实请求

选择后立即检测:

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 / 官方文档为准。

安装提示

只在用户确认后安装;不要在未确认时自动安装依赖。

# 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 采样结果。

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,
    )

对齐验收示例

对齐成功后,检测端点应至少达到以下级别:

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 调用。

'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。

// 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。

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 记录使用版本和参数。

伪代码:

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 确认不包含浏览器自动化代码和多余产物。

输出模板

## 最终请求验证

- 是否执行真实请求:是 / 否
- 用户授权:是 / 否
- 前置阶段已选客户端: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 或挑战结果。