HTTP/3 测试 & 网络指纹实验室
两部分:① 检测「当前浏览器 + 网络」能否与本站建立 HTTP/3 (QUIC) 连接(读 PerformanceResourceTiming.nextHopProtocol,h3=HTTP/3、h2=HTTP/2、http/1.1=HTTP/1.1);② 采集本连接的服务端协议指纹(JA3/JA4、PeetPrint、Akamai HTTP/2、HTTP/3 指纹)——用于反欺诈研究:UA 声称的浏览器与真实指纹是否一致。
⏳正在测试…首次请求通常走 h2;浏览器看到服务器广播的 h3(Alt-Svc)后,后续请求才会尝试 QUIC。请稍候几秒。
综合风险分(反欺诈)
正在综合四层信号评估…
本设备历史访问次数
本设备关联过的 IP 数
本 IP 背后的设备数
近 1 小时本设备访问
综合
四层(TLS 协议指纹 / 网络 TCP-IP / IP 信誉 / 客户端设备)信号,叠加
历史关联——同设备指纹簇、同设备多 IP(代理轮换)、单位时间高频(velocity)、单 IP 多设备(设备农场)——给出
可解释风险分。
→ 打开关联层仪表盘(近 200 条访问 + 风险明细)。数据存于本机 SQLite,仅用于本实验演示。
HTTP/3 协商结果
最终协商协议—
成功用上 HTTP/3 的比例—
本页加载(导航)用的协议—
服务器广播 h3 (Alt-Svc)检测中…
首次协商 h3 用了几次请求—
平均往返(responseEnd-start)—
逐次请求明细
DNS=域名解析;连接=TCP/QUIC 建连;TLS=加密握手;TTFB=首字节;单位 ms。h3/复用连接时部分阶段可能为 0。
服务端协议指纹(反欺诈)
正在从指纹服务(:8443)采集本连接的 TLS / HTTP2 / HTTP3 指纹…
指纹连接协议—
TLS 版本(record / 协商)
JA3(完整串)
JA3 hash
JA4
JA4_r
PeetPrint (TLS1.3)
JA4H(HTTP 头指纹)
Accept-Language(服务端所见)
Akamai HTTP/2 指纹
Akamai H2 hash
HTTP/3 指纹
服务器看到的 UA
UA 与指纹一致性
怎么用于反欺诈:TLS 栈(JA3/JA4/PeetPrint)与 HTTP2/3 帧顺序由客户端底层库决定,伪造成本高。把这些 hash 与已知真实浏览器指纹库(如 tls.peet.ws)比对——若 UA 自称 Chrome,但 JA4/Akamai 指纹对应的是 Python/Go/curl,即高度可疑(脚本/爬虫/伪装)。这些指纹浏览器 JS 拿不到,必须服务端读原始 TLS/QUIC 帧,本页由独立指纹服务(TrackMe)在 :8443 提供。
▸ TLS 原始:密码套件 / 扩展 / PeetPrint
▸ HTTP/2 原始帧(SETTINGS / WINDOW_UPDATE / PRIORITY / 伪头顺序)
会话恢复 / QUIC 深度(A3 · A4)
会话恢复(TLS PSK)
PSK ticket 句柄
QUIC 0-RTT
QUIC 版本 / GSO / DATAGRAM
QUIC transport params 顺序
TP 指纹 / 参数个数
H3 SETTINGS
会话恢复:客户端复用上次的 session ticket 时,ClientHello 会带 pre_shared_key(41),
里面的 ticket identity 是一个跨连接、跨 IP 都稳定的句柄 —— 它由 TLS 栈管理,JS 层改不掉,
比 canvas 更难规避。反过来,同一设备访问多次却从不复用,说明每次都是新容器/新配置(农场特征)。
QUIC 连接同样会在 ClientHello 里带 PSK(实测 Chromium 走 h3 时
pre_shared_key 与 used_0rtt 会同时出现)。首次访问因为还没有 ticket 可复用,
这一项显示"未复用"是正常的 —— 要看到复用,需要该浏览器此前访问过本站。
QUIC transport parameters 的参数顺序是客户端 QUIC 实现的指纹,
与 SETTINGS/Akamai 指纹互相独立。上游只给原始 hex,这里用 QUIC varint 在前端解出顺序与取值。
内核 RTO / 端点可达性(A5 · E1)
正在探测黑洞端口与备用端点…(约 12 秒)
内核推断(SYN 重传)
重传间隔(ms)
与协议栈指纹是否一致
端点可达性
各端点观测到的 MSS
IPv6 出口
多锚点 RTT(地理一致性)
SYN 重传曲线:向一个有包能进、但无人应答的端口(8445)发起连接,
客户端内核会自己重传 SYN。重传间隔暴露的是内核 RTO 实现,不同系统差别很大 ——
Linux/Android 是 1s 起步每次翻倍,Windows 约 3s 起步且只重试两次,macOS/iOS 前几拍不退避。
这个信号在 JS 层完全改不掉(是内核行为),和 UA 自称的系统对不上就是硬矛盾。
端点可达性:同时打 :8443 与 :2053 两个指纹端点。
2053 是 Cloudflare 常用端口,被运营商干扰的概率低于 8443 —— 两个都打才能看出
"哪个端点在哪家网络更可达",也验证主备切换是否真的生效。
IPv6 出口:很多代理/VPN 只接管 IPv4,IPv6 直接走原生链路 ——
这时 v4 显示代理出口、v6 却暴露真实地址,是经典的代理绕过。
双栈环境下二者属于不同 ASN 尤其可疑。
多锚点 RTT:同时测到国内/美国/新加坡几个位置确定的锚点,
最近的那个才是你物理上所在的区域 —— 与 IP 声称的位置对不上就是矛盾。
⚠️ 部分网络会对未监听端口代答 RST,那样客户端立刻放弃、不会重传 ——
此时本项会显示"未观察到重传",这是该网络的特性,不是探测失败。
真实地理位置研判(多源交叉)
正在多源交叉定位…
研判结论
置信度 / 依据数
各信号指向
RTT 物理下界排除
检出的矛盾
把所有带地理含义的信号放在一起投票,而不是只看 IP 归属。
客户端能改的(浏览器时区、语言)与改不了的(出口 IP、DNS 解析器归属、
ECS 子网、RTT 物理下界、Cloudflare 接入机房)分开计权 —— 后者对不上前者时,以后者为准。
RTT 能排除什么:光在光纤里约 200km/ms,所以 RTT 给出的是距离上界 ——
"你不可能离锚点 X 超过 N 公里"。它只能排除"太远",不能排除"太近"
(绕行与排队只会让 RTT 变大),所以单锚点无法收敛,要靠多个方向的锚点取交集。
能做到什么精度:城市级(靠 IP/DNS/ECS 三方交叉)到区域级(靠 RTT 排除)。
做不到街区级 —— 那需要 WiFi BSSID 查库,本项目主动不做(隐私)。
网络层 · TCP/IP 指纹与代理判定
正在读取本连接的 TCP/IP 指纹(需服务端从 SYN 抓取)…
代理/伪装判定
TCP 推断系统
UA 声称系统
观测 TTL → 初始 TTL(跳数)
MSS 原值 → MTU(判定)
PMTUD / DF 位
TCP 窗口 / 窗口缩放
TCP 选项(原始)
TCP 选项顺序
IP ID / TOS / 总长
TCP 时间戳 TSval(时钟/NAT 线索)
p0f 式协议栈签名
怎么用于查代理:TTL/MSS/窗口/选项顺序由终结这条 TCP 连接的那台机器的操作系统内核决定。
① MSS<1460(MTU<1500) → 链路上有隧道封装,疑似 VPN(WireGuard≈1420、OpenVPN≈1400)。
② TTL 推断的系统 ≠ UA/TLS 声称的系统(如 UA 说 Windows、TTL 却是 *nix)→ 中间隔着 Linux 代理机,疑似代理/伪装。
透明代理(住宅代理/SOCKS)不改 TLS 指纹,但这一层的 MSS/OS 线索仍可能露馅。若本卡显示"不可用",通常是连接经了 NAT/负载均衡,原始 SYN 在本机不可见。
IP 信誉 / ASN(机房 vs 住宅)
正在查询 IP 归属(离线 ASN 库)…
IP 类型判定
网络类型(原始分类)
客户端 IP
ASN
机构 / 运营商(原始)
IP 国家 ↔ 浏览器时区
语言 ↔ 国家 ↔ 时区 三角
rDNS(反向解析)
握手 RTT ↔ 地理(物理下界校验)
怎么用于查代理:机房/托管 ASN(阿里云/腾讯云/AWS/OVH…)几乎不会是真实终端用户 → 命中即高度疑似 VPS/代理/爬虫;住宅代理则要靠 ASN + 上面的 TCP/MSS 线索综合判断。
地理↔时区不一致(IP 在美国、浏览器时区却是 Asia/Shanghai)也是代理的常见破绽。离线 ASN 库来自 iptoasn.com,无第三方请求。
DNS 行为(自建权威 DNS 实时观测)
正在触发一次唯一子域解析,观测你真正使用的递归解析器…(约需数秒)
判定
解析器池规模
递归解析器(逐台)
EDNS Client Subnet(ECS)
客户端出口 IP
并发向 16 个<随机>.d.h3.investigate.bio 触发解析,由本站自建权威 DNS 记录真正来查询的递归解析器 IP 与 ECS(原始值)。
为什么要发多个:解析器池会轮转,单次查询只落到其中一台 —— 实测同一网络下 12 个 token 能看到 5 台解析器,
其中一台还在完全不同的网段,只发 1 个有 4/5 概率漏掉它。解析器分布在多个 ISP 本身就是信号(DoH/分流/代理)。
解析器与你不在同网、或 ECS 子网与出口 IP 不符,常暗示公共 DNS / DoH / 代理;Cloudflare(1.1.1.1)默认不发 ECS(隐私)。这是浏览器完全拿不到、只能在权威侧观测的信号。
客户端设备指纹(高熵 · 设备关联)
正在采集浏览器高熵指纹…
设备指纹 deviceId
Canvas · 几何(矢量路径/抗锯齿,参与 deviceId)
Canvas · 文字(系统字体)(吃系统字体包,预期随ROM漂移)
Canvas · 文字(内嵌字体)(本站自带字体,排除字体版本这一变量)
↳ 内嵌字体确认生效?(独立校验,不是"相信我")
Canvas · emoji(彩色字体版本,预期漂移最剧烈)
Canvas · 渐变合成(GPU 合成管线)
WebGL GPU 厂商
WebGL GPU 型号
GPU → 系统提示
AudioContext 指纹
已装字体(检出/探测)
屏幕 (宽x高xavail·色深·DPR)
CPU 核 / 内存(GB)
时区 / 偏移
语言
platform / 触摸点
怎么用于查同设备/批量注册:Canvas、WebGL(GPU 型号)、AudioContext、字体列表是高熵信号,组合后常接近唯一 → 即"设备指纹",能把多个账号关联到同一台机器。
服务端 TLS 指纹熵太低(百万人同为 Chrome/Win),做不到设备级关联,必须靠这一层。
三层交叉:WebGL 的 GPU 暗示的系统、UA 声称的系统、网络层 TTL 推断的系统三者应自洽——不一致往往是 anti-detect 浏览器 / 虚拟机 / 伪装。
此指纹在浏览器本地计算,随本页的其余检测结果一并上报给本站服务端用于关联分析,不发往第三方。
为什么把 Canvas 拆成多层:起因是有用户实测同型号手机、刷不同 ROM 后 Canvas 指纹会变,
但底层是同一颗芯片。原来一张画布混着矩形、文字、描边弧线一起算一个哈希,没法分辨"这次变化到底出在哪一层"。
现在拆成 几何(纯矢量路径+抗锯齿,不含文字/emoji)、文字·系统字体(多语种字体渲染)、
文字·内嵌字体、emoji(彩色字体)、渐变合成(GPU 混合管线)独立哈希。
deviceId 现在只由"几何"层参与计算(连同 GPU/Audio/字体列表),文字与 emoji 层被排除在设备身份之外——
这些层预期会随系统/ROM 的字体包更新而漂移,继续把它们混进身份哈希,只会让"同一台设备升级一次系统"
在系统里变成"凭空多出一台新设备"。
"文字·内嵌字体"这一层是怎么回事:第一轮上线后用户实测反馈"同 ROM 的两台手机四层完全一致,
换了 ROM 只有文字层不一样"——为了确认这到底是字体文件/版本的差异,还是渲染引擎本身的差异,
这一层改用本站自带、打包进页面的字体文件(Roboto Mono 拉丁字符子集,Apache 2.0 协议,内嵌为
data: URI,不发起任何网络请求)绘制同类文字,而不是调用系统装的字体。所有设备加载的字节完全相同——
如果这一层也随 ROM 变,说明差异出在渲染引擎(hinting/抗锯齿实现);如果这一层不变而"系统字体"那层变,
说明差异就是系统字体文件本身随 ROM 升级了。只测拉丁字符:内嵌字体没有 CJK/emoji 字形,混进去
会触发系统字体回退,又把变量混回去了 —— 所以这一层的结论只能推广到拉丁文字部分。
"↳ 内嵌字体确认生效?"这一行是干什么的:字体加载是异步的,有可能悄悄失败又悄悄回退到系统字体
而不报错 —— 那样的话"文字·内嵌字体"这层其实又变回了"系统字体"的另一份拷贝,结论会整个反过来。
这一行不是"相信我加载成功了",而是独立测量:量出这台设备上 canvas 实际画出的 'M' 字符精确宽度,
跟这份字体文件自带的字形表算出的理论宽度(9.6016px,fonttools 读 hmtx 表核出的)做比对,还额外测了一次
"请求一个压根不存在的字体名"作为反例。两个条件都满足才算真生效:测出的宽度精确匹配理论值,
且明显不同于那个不存在字体的回退宽度。拿两台不同 ROM 的手机比较"文字·内嵌字体"哈希前,先看这两台
上这一行是不是都显示"✅ 确认生效"——如果任何一台显示"⚠️ 可疑",那台的对比结果不可信,不能拿来下结论。
✅ 2026-09 真机确认的结论:用户在同型号、不同 ROM 版本的手机上实测(两台"↳ 内嵌字体确认生效?"
均通过,结果可信)——几何 / emoji / 渐变合成 / 文字·内嵌字体这四层都不随 ROM 变,只有"文字·系统字体"
这一层会变。也就是说:Canvas 文字层随 ROM 漂移,根因是系统字体文件本身随系统更新换了版本,
不是渲染引擎(hinting/抗锯齿实现)本身发生了变化。这个结论已经写进 `docs/detection-capabilities.md`
并同步进了后端的观测规则措辞(只变系统字体层 vs 两层一起变,现在会给出不同的判断提示)。
⚠️ 这仍是小范围机型对照的结果,不代表对所有芯片/厂商/ROM 组合都成立 —— 后续遇到反例会回来更新。
客户端深度检测(机器人 / 反检测 / Client Hints)
正在做自动化/反检测/Client Hints 检测…
自动化 / 无头判定
命中项
反检测浏览器(指纹随机化)
Client Hints ↔ UA/GPU 一致性
UA-CH 平台 / 架构 / 位数
UA-CH 完整版本
时区 / 语言 一致性
自动化检测抓 navigator.webdriver、HeadlessChrome、Selenium/Puppeteer/Playwright 痕迹、权限矛盾等 → 脚本/爬虫。
反检测浏览器把 Canvas/WebGL/Audio 各跑两次——真浏览器结果恒定,注入随机噪声(Multilogin/GoLogin 等)则两次不同 → 批量注册工具。
Client Hints 的平台/架构与 UA 字符串、GPU、TCP 推断的系统应自洽,不符即伪装。
篡改检测(原生函数 / 描述符 / 跨 realm)
正在检查关键 API 是否被改写…
判定
已检查的原生函数
被改写的函数
属性描述符异常
跨 realm 差异(iframe 对照)
Error 栈形状
字形度量指纹
上一层"反检测浏览器"看的是值稳不稳(两次采样是否一致);这一层看的是有没有动过手脚的痕迹——
即使伪造器把值做得很稳,只要它是用 JS 包了原生函数,Function.prototype.toString 就不再是
{ [native code] }。描述符异常抓"把 data property 改写成 getter"这类伪装
(常见于伪造 navigator.webdriver/hardwareConcurrency)。
跨 realm 用新建 iframe 取干净引用做对照,主 realm 被改过就会对不上。
字形度量比"数字体个数"熵高得多:同一串字在各字体下的精确宽高由字体文件与渲染引擎共同决定,
量化到 0.01px 后哈希——同机同浏览器必须完全可复现。
这些信号目前只观测不计分,等积累足够标注样本、算出各自的命中率与假阳性率后再赋权。
WebRTC IP 泄露(穿透 VPN/代理)
正在通过 STUN 收集 WebRTC 候选地址…
判定
WebRTC 公网 IP(srflx)
本地/局域 IP(host)
对比:服务端出口 IP
WebRTC 经 STUN 能拿到浏览器真实公网/局域 IP,可穿透 VPN/代理——与服务端看到的出口 IP 不符即暴露真实位置(经典查代理手段)。现代浏览器用 mDNS 隐藏局域 IP,但公网 srflx 候选仍可能泄露。
客户端·渲染 / 媒体 / 持久化
正在采集语音/编解码/emoji/WebGL/CSS/持久ID…
语音/emoji/编解码由 OS 决定 → 与 UA 声称系统交叉验证。持久 ID 冗余存于 localStorage+IndexedDB+cookie,清 cookie 也能重认——同一持久 ID 出现在不同设备指纹下 = 有人在清指纹/多开规避。纯本地,仅本实验演示。
客户端·行为与运行环境
正在观测交互与运行环境(约 6 秒;动动鼠标数据更全)…
交互存在性
鼠标行为(类人/机器人)
指针/悬停 ↔ 设备类型
CPU 微基准
计时器最小分辨率
requestAnimationFrame 帧率
无交互 + 机器人式鼠标(直线/匀速/瞬移)→ 脚本;指针/悬停与设备类型不符(移动 UA 却支持 hover)→ 伪装;CPU/计时/帧率异常(帧率<30、计时被粗化)→ VM/无头。行为需你在页面上动鼠标才有样本。
环境信息
测试时间
浏览器 / 版本
操作系统 / 平台
网络类型(如可读)
安全上下文 (HTTPS)
协议检测 API
页面地址
User-Agent
结果怎么读 / 排查
h3 出现 = 本浏览器与本网络成功建立了 QUIC 连接,UDP 443 全程通(客户端 + 网络 + 服务器都 OK)。
h2 始终 = 用不上 HTTP/3。本页测的是「到本服务器」的 h3,失败可能出在任一环节,请按下面排查:
- ⭐ 浏览器缓存(最常见):若外部工具(如
http3check.net)显示本服务器支持 h3、但本页始终 h2 —— 是你的浏览器把这个域名的 h3 标记为"坏"了(之前失败时留下的 broken alt-svc 退避,可长达数小时)。最快解法:用无痕/隐身窗口重开本页;或清除浏览数据后重试;Chrome 也可在 chrome://net-internals/#sockets 点 "Flush socket pools"。
- 服务器端:本站是否放行了入站 UDP 443(不是只开 TCP 443)。QUIC 走 UDP,很多人只开了 TCP。
- 客户端网络:所在网络/防火墙是否屏蔽出站 UDP 443(企业网/校园网常见)。可对照打开
https://cloudflare-quic.com——若那边显示 HTTP/3 而这里不行,则问题在本服务器;若那边也用不上 h3,则是你的网络屏蔽了 QUIC。
- 浏览器:是否为较新的 Chrome/Edge/Firefox,且未禁用 HTTP/3(Chrome:
chrome://flags 的 Experimental QUIC;企业策略也可能禁用)。
- VPN / 代理:是否强制所有流量走 TCP(很多代理不转发 UDP/QUIC)。