被爬虫光顾的一夜
第 11 篇夸完趋势递增,老王就挨了现实一巴掌:风控报警,某接口凌晨被同一 IP 刷了两万次,URL 长这样——/order/detail?no=202609140001、202609140002、202609140003……一路连号试过去。订单号规律得像流水线,爬虫顺着号遍历,撞上一个未做归属校验的老接口就拖走一单数据。递增的甜头刚吃完,可预测性的苦头跟着到账。这一篇算安全账。
ID 到底泄露了什么
把 ID 摆在明面上,等于给外人发了一份业务简报:
规模与增速:号段模式看跳变就知道每段发多快;雪花 ID 解出时间戳,单量曲线直接画出来——竞对盯你大促战报,不用看新闻,看号就行。
基础设施规模:雪花 ID 里 workerID 占着固定几位,解出来就是机器数量与拓扑;UUID v1 更狠,直接带 MAC 地址。
遍历入口:连号 ID 是给爬虫递梯子,配合没做归属校验的接口,就是数据泄露事故——业内管这类漏洞叫 IDOR(不安全的直接对象引用),常年霸榜 OWASP。
第一道防线:归属校验,没有商量余地
先把话说死:ID 不可预测是优化项,归属校验是必选项。拿到订单号的人必须先过登录态,再校验这单是不是他的——后端逐条判断,别信前端传参。这一步做了,ID 连号也只是难看;这一步不做,ID 再乱也挡不住越权。老王翻代码发现出事的接口压根没做归属判断,补上校验,漏洞先止血。但校验挡不住的还有一层:正常用户之间互相猜对方的单号做关联分析、给竞对递数据——这就要让 ID 本身不可猜。
第二道防线:外部 ID 与内部 ID 分离
架构上最干净的做法:内部主键照旧用趋势递增 ID 吃 B+ 树红利,对外暴露一个加密映射后的外部单号,两者一一对应,密钥只在服务端:
下单:内部 id=202609140001 对外 order_no=Enc(202609140001)=7f3kQ9mA
查询:按 order_no 反解出 id,再走内部逻辑
落库:订单表存 id,冗余一列 order_no 建唯一索引加密映射的主流做法:
Feistel 网络:把 64 位 ID 对半拆开,轮函数迭代八轮,输出还是 64 位、一一对应、可用密钥解回来。长度不膨胀、格式不变(还能伪装成日期前缀的样子),Instagram 短链 ID 加密用的就是这套思路,工程验证充分。
格式保留加密(FPE/AES-SIV):标准密码学组件,安全等级高,适合合规要求严的场景,接入成本略高。
Hashids 类混淆:严格说是编码不是加密,密钥藏在参数里,防君子不防专家,仅适合无敏感信息的场景(比如防止文章 ID 被遍历抓取)。
顺便点名两个反面教材:MD5(id) 做外部号——查表穷举就破,且碰撞理论存在;简单 XOR 或仿射变换——拿两组样本做差就还原密钥。加密映射请走正规密码学路径,密钥进配置中心而非代码。
第三道防线:让 ID 本身不可猜
不想引入双 ID 映射的团队,可以在发号器上动刀,牺牲一点递增性换不可预测性:
跳号:号段模式每次取号随机丢弃一段(比如每次跳过 0-9 个),外部看到的号有洞、间距不均,遍历成本指数上涨。实现一行代码,对 B+ 树影响可忽略——洞也是趋势递增。
时间位加盐:雪花的时间位与真实时间做一次可逆变换(如异或一个固定盐再重排),不解盐就推不出真实单量时间曲线。注意别把盐设计成可被统计还原的线性关系。
限流与风控兜底:接口层对单 IP、单账号的 ID 探测频率限流,异常遍历模式直接进黑名单——这是所有方案的最后一道闸。
三层防线怎么选
| 方案 | 防什么 | 代价 | 适用 |
|---|---|---|---|
| 归属校验 | 越权(IDOR) | 每接口多一次判断 | 必须做,无条件 |
| 外部 ID 加密映射 | 遍历、规模泄露 | 双 ID 维护、一次反解 | 订单、用户等敏感对象 |
| 跳号/加盐 | 遍历、增速泄露 | 号有洞、少量改造 | 不想双 ID 的中低敏场景 |
| 限流风控 | 自动化攻击 | 风控平台接入 | 全站兜底 |
小结
安全账三句话:归属校验是根,缺了它 ID 再乱也是裸奔;外部 ID 加密映射是墙,Feistel 换来可预测与安全的兼得;跳号加盐是烟幕,一行代码提高遍历成本。老王的订单体系最终上了双 ID:内部号段 ID 伺候 B+ 树,外部 Feistel 加密单号伺候用户——两本账,各算各的。ID 体系还剩最后一块拼图:分库分表之后,ID 能不能顺便把路由也干了?下一篇讲分片基因。
评论 (0)