订单链接就是钥匙:它暴露了什么
2026 年 11 月 3 日我们建了一笔真实的 100 USDT 订单,再用零 Cookie、零鉴权把它读回来:十个字段、永不过期、还有一个第三方脚本会收到完整 URL。订单链接到底是什么,一次实测。
2026 年 11 月 3 日 10:03:11 UTC,我们在 veilswap.io 上真实建了一笔单:100 USDT(TRON)换 XMR。接口返回五个字段——订单号 4192151d-…、入金地址 TUTmcejZAXe2nGhoJ6CJ5Fz8qHn9QMuvTs、0.13951129 XMR、状态 waiting_deposit,以及 601 秒后的截止时间 10:13:12。此后的一切都通过一个字符串发生:https://veilswap.io/order/4192151d-04c6-4595-936c-48b15a4ad0c6。没有账号、没有邮箱、没有密码。链接就是凭据,这篇讲的是它把什么交出去、交给谁、交多久。 (XMR 换 USDT 退出路线)
这个设计是刻意的,代价也是。 无账号的兑换站没法让你登录,只能给你一个可以带走的东西:URL。orders 表二十个字段里没有 email、phone、name——既没有第二条找回订单的通道,也没有第二个能把你和某笔购买绑起来的标识。这就是交换:少一条链,多一个必须自己保管的秘密。强度是真实的:生产库里 143 笔订单全部是 uuid v4,122 位随机;按每秒十亿次猜测,找到一个的期望时间约 1.7×10^20 年。所以攻击不在猜,在携带。
链接到底交出了什么。 不带 Cookie、不带 Authorization 请求 GET /api/order/<id>,返回 200 和十个字段:depositAddress、depositExtraId、fromAmount、fromAsset、toAsset、toAmount、status、providerStatus、validUntil、id,响应头是 cache-control: no-store。它不返回 to_address 与 refund_address——这两个留在服务端。订单进入终态后,同一个接口连 payinHash、payoutHash 也不再返回:我们在一笔已支付的单上核对过,两个键都消失了。所以泄漏的链接告诉陌生人你在换什么、换多少、该盯哪个入金地址、走到哪一步;它不告诉你币最终去了哪,结算后也不给交易哈希。
过期的只有付款窗口,不是链接。 那 601 秒是入金窗口。10:04 UTC,我们回读了一笔 09:24:55 建单、已退款的订单,入金地址与 validUntil 仍然原样返回,距建单 39 分钟。代码侧一致:订单路由只导出 GET,没有 PATCH、PUT、DELETE,所以没有轮换、没有吊销、没有「删除订单」。还有一个细节让这件事更锋利:143 笔订单只对应 134 个不同入金地址,其中两个地址分别承接了 7 笔与 4 笔;当链接指向这样的地址时,它指向的是一个被多笔订单用过的共享地址,而不是你的私有地址。地址复用是池方的分配行为,不是链接的 bug,但它改变了链接所揭示的东西:不只是你的单,而是一个有多笔单经过的地址。
三个本地副本,全在我们够不到的地方。 浏览器历史存 URL,浏览器同步再把它复制到每一台登录设备。订单页的截图通常同时包含二维码(编码的就是入金地址)与地址栏。交易组件还会把 vs_active_order 写进 localStorage,内容含订单号、入金地址、extra id、两个金额与两个币种 id;只有在收到终态时才删除。在共用笔记本上没付款就关掉标签页,那个配置里还留着你昨天的入金地址——sessionStorage 的 vs_order_session 只决定回 /exchange 要不要自动弹回旧单,不决定这份记录留不留。这三个副本我们都看不到,也都无法从我们这侧吊销,所以链接只能当成「你随身携带的东西」,而不是「站点替你看管的东西」。
页面本身带了一个能看到 URL 的第三方。 /order/<id> 的 HTML 是 32,795 字节,带 noindex、nofollow 的 robots meta,不设 Cookie,cache-control: no-store。它加载 01a0d6f6-4444-7a6d-a45f-6375a01eba8f.spst2.com 上的 ustat.js(3,560 字节)。读这个脚本:它把 location.href 放进 url 字段,收集 referrer、屏幕尺寸、语言与 canvas 指纹,再 POST 到 /api/report。兑换页上跑的 Clarity 在订单页是 0 处——这块已经排除了——但这个更小的统计脚本仍然收到完整订单 URL。referrer-policy: strict-origin-when-cross-origin 只能把跨站请求的 Referer 裁到域名,挡不住你亲自加载的脚本去读 location。这是唯一一条完全离开我们基础设施的披露路径。
在我们这边,链接落进一行日志。 nginx 格式记录 remote_addr、来自 CF-Connecting-IP 的真实客户端 IP、时间、含完整 UUID 的请求行、状态、字节数与 UA。日志按天轮转、保留 14 份,所以任何时刻都存着约 15 天的「IP↔订单」映射。这个窗口里我们数到:订单页被打开 83 次、API 被轮询 738 次、涉及 149 个订单号;61 条不同订单路径中 6 条来自一个以上客户端 IP;API 侧 8 个订单被一个以上地址轮询,最多 3 个。4 条路径被 bot UA 抓过——包括 TelegramBot (like TwitterBot)——其中 3 条同时有人类 UA,这就是「链接被贴进聊天」在服务端的样子:预览机器人在几分钟内就到,比收件人点击还早。404 侧是 120 次请求、82 条路径、9 个 IP,最忙的一个打的是 /api/order/1 与 /api/order/callback——通用扫描器,不是枚举。没有见到成功的暴力尝试,122 位下也不该见到。
拿到链接的人能做什么、不能做什么。 不能:改收款地址、改退款地址、取消订单、把钱转走——没有写接口,两个地址建单即冻结;往入金地址打钱只是替你这笔单付款。能:读到金额与币对、在链上盯住入金地址、想多勤就多勤地轮询状态——我们连发 40 次 GET,得到 40 个 200,因为读接口没有应用层限速(报价有限速,建单是 6 单/小时/IP)。真实损害是披露加社工:知道你订单号、金额、并且知道「还没付款」的人,可以拿着只有内部人才该知道的信息冒充客服找你,也可以守着入金地址等你打款。后一种更安静:一个被盯住的入金地址会暴露你的时间点,而时间点往往足够把一笔兑换和它之前发生的事连起来。 (转错网络如何找回)
一套不花钱的习惯。 一,别把整条 URL 贴进聊天、工单或截图:要发就发入金地址与金额,或者把地址栏裁掉。二,要留存就存进密码管理器条目(按日期 + 币对命名),别只留在浏览器历史里。三,订单到 paid、expired、refunded 之后回页面开一次,让组件清掉 vs_active_order;共用机器再清一次站点数据。四,别人的电脑上用隐私窗口。五,如果必须求助,就接受要交出完整订单号——客服没有它查不到单,这也是这个秘密唯一必须出门的地方,所以用你会用来发密码的渠道。六,把定位摆正:链接是「读权限」,不是钱包。丢了不会丢币,因为已付款的单照样结算;给了也不会让币被转走,因为地址改不了。有两处值得点名:统计脚本不该在订单页运行——它把完整 URL 交给第三方;入金地址复用该在页面上标出来——现在 143 笔订单共用 134 个地址。