Files
blog/content/posts/tab-and-overreach.md

5.8 KiB
Raw Permalink Blame History

title, description, date, draft, tags
title description date draft tags
一个制表符,一个越权 周五收工复盘:KAP 平台两个同名客户差一个 ,派单挂错联系人;行级隔离上线当天,复盘揪出 6 个详情类越权端点。安全这件事,永远栽在'看起来一样'和'只做了一半'上。 2026-08-28T19:35:00+08:00 false
安全
权限
排障
复盘

背景

周五,两件大事收尾:给自建运维平台上线了工号绑定 + 行级数据隔离(12 人企微用户导入),以及修复了一起三方派单挂错联系人的生产事故。

一件是新建安全机制,一件是老安全机制失效。巧的是,它们的教训互为镜像。

一、一个制表符引发的事故

派单后,第三方平台的工程师端显示的是历史联系人,不是工单上的当前联系人。

排查下来,链条很优雅地坏着:

第三方平台存在两个"同名"客户:
  "\t上海某某信息技术股份有限公司"  ← 带一个制表符前缀,主联系人=历史联系人A
  "上海某某信息技术股份有限公司"    ← 正常,主联系人=正确联系人B
  • strip 之后名字完全相同,列表接口返回的还是脱敏名(上海****),精确匹配在生产环境永不命中
  • 代码逻辑是「找到就 break」——盲取第一条,命中了带制表符那位
  • 更糟的是,一次 update 把正确客户的地址写进了历史客户档案(地址污染

修复:盲选改裁决

候选 >1 时全部拉详情,按四级裁决:

  1. detail.name 精确匹配
  2. 主联系人电话归一化对比(实测唯一可靠的裁决键)
  3. 地址对比
  4. 全部失败 → 报 ambiguous 转人工,绝不盲选

顺手把另外两家平台排查了一遍:修吧有同类问题(原来传的是公司名,联系人姓名整个丢失)已补传;大鱼链路本来就传联系人,无需改。

教训matched = c; break 这种「找到第一条就用」的写法,在数据源不可控(存在脏档案、脱敏名、同名客户)时就是定时炸弹。候选不唯一时,宁可报错转人工,也不要猜。

二、隔离上线当天,复盘揪出 6 个越权

另一条线是给平台加行级数据隔离:每个工程师只能看自己工号的数据,SN 脱敏(4CE2***Y2),管理员看全量。

上线过程很顺利——5 个数据面接入隔离,端到端验证:普通用户看 10 条,admin 看 47 条,全对。

如果故事到这里结束,这篇文章就不存在了。

晚上复盘时换了个方法:不查「已接入的端点」,而是 grep 全仓所有带 gh_id 的模型,反查它们每一个查询方法是否都过了隔离。结果揪出 6 个漏网的——全是详情类端点

端点 越权方式
getTrend 传任意 SN 查别人设备的健康趋势
getDetail 传任意 SN 看客户名、工单号
getRepairs 传任意 SN 查维修记录
errorStats 全局统计里返回完整 SN
warrantyExpiring 保修列表未按工号过滤
findById 传任意 id 查诊断报告详情

为什么会漏? 列表类端点的查询条件里天然带 gh_id(WHERE 一加就隔离了);而详情类端点按主键/SN 查单条,查询条件本身不含工号——需要额外反查这条数据的归属人。接入清单是按「页面/功能」列的,详情接口藏在「页面能正常打开」的假象里。

修复后:6 项越权全部拦截,admin 回归正常。

镜像:两件事互为对方的教训

制表符事故 越权补漏
出事的层 数据层(脏数据 + 盲选代码) 权限层(只防了列表没防详情)
根因模式 「strip 后一样」→ 误判唯一 「列表能看对」→ 误判全防住
正确姿势 候选不唯一 → 四级裁决,绝不盲选 隔离要覆盖所有查询路径,不只列表

共同点就一句话:验证的时候,你测的路径和攻击者走的路径,往往不是同一条。

  • 验证「客户匹配」用的是干净数据,生产里是脱敏名 + 制表符脏档案
  • 验证「隔离生效」点的是列表页面,攻击者直接构造详情请求

今日其他(速记)

  • edit_wo 维修站歧义:用户传「武汉长租」连败 5 次,根因是子串匹配没过滤排除词——候选里只剩「美邦武汉长租」「极算武汉长租」,正确的「总仓(武汉)长租」因含括号反而匹配不上。create_wo 早有排除词机制,edit_wo 漏了(同源机制没同步的又一例)
  • 巡检单 46% 建错站:rent_type=未知时默认长租没生效,③级按 API 顺序被短租站稳定截胡。教训:默认值必须覆盖"未说"的分支,取第一个匹配必须显式优先级
  • AI 网关 504:商汤长 prompt 挂起撞 nginx 120sfallback 没来得及跑——把 curl 超时降到 90s 让降级生效,顺手接入 MiniMax 做第三梯队
  • eff_stats 每日双跑:调度器只触发 1 次,是 agent 模型在同一会话里执行了 2 遍脚本。cron prompt 加「严格只执行一次」,明晨验证

总结

今天的关键词是「补全」:

  1. 数据不可信时,代码要能裁决——同名客户、脱敏名、脏档案,盲选第一条就是赌博
  2. 权限不能只防"正门"——列表是正门,详情是侧门,越权往往从侧门走
  3. 复盘方法比复盘本身重要——「grep 全部 gh_id 模型反查接入面」这个动作,比发现这 6 个漏洞更有复用价值
  4. 默认值也是一种决策——「用户没说」不等于「随便选」,46% 的错误率就是没想清楚默认值的代价

安全的敌人从来不是攻击者,是「看起来已经防住了」。


本文已脱敏,不含真实客户名、联系人、电话或系统密码。