在粘贴之前先脱敏你的秘密
脱敏一个秘密,是指在文本被分享之前,把其中的凭据——API 密钥、令牌、密码或私钥——替换成一个标记,使信息可以传递而不传递访问权。它之所以重要,是因为秘密最常见的泄露方式不是攻击,而是一次粘贴:把日志粘贴进支持工单,把配置文件粘贴进聊天,把调试命令粘贴进论坛帖子。本文解释为什么那些粘贴如此难以撤销,秘密脱敏工具这样的扫描器能捕获什么、不能捕获什么,以及那套让热心粘贴不变成事故的流程——在本地脱敏、分享脱敏副本、轮换任何已经逃出去的东西。
为什么秘密会出现在工单、聊天和日志里
没有人故意粘贴秘密。他们粘贴的是秘密周围的东西:碰巧包含请求标头的错误日志,碰巧在下面第三节藏着数据库密码的配置文件,从终端复制出来的、令牌正坐在 Authorization 标头里的 curl 命令。排障靠的是上下文,而凭据就住在上下文里——这就是为什么支持台、聊天频道和问题跟踪器会悄悄积累活生生的密钥、会话令牌和私钥,写下它们的是匆忙中只盯着错误消息、没看上面那行标头的小心的人。
然后,接收系统以发送者从未见过的方式让那次粘贴成为永久的。工单被搜索索引、被转发给供应商、被导出成报告并保存多年;聊天消息被推送到手机通知中、被在话题中引用、被机器人总结;日志被运送到聚合器,那里有自己的保留期和访问列表。这样的系统中的一个秘密不在一个地方——它在系统生成的每一份副本、备份、预览和通知里,其中大多数发送者既列不全,也删不掉。这一秒粘贴、却无法可靠召回的不对称,正是脱敏必须发生在粘贴之前、发送者的机器上的全部理由,而不是事后的清理。
扫描器能捕获什么:形状、校验和与结构
好消息是,真实凭据高度模式化,而模式扫描是确定性的:同样的文本总是产生同样的发现,中间没有任何模型在猜测。服务商密钥带有公开的形状——前缀、固定长度、受限的字符集——因此 AWS 访问密钥、GitHub 令牌、Stripe 密钥或 Slack 令牌可以一眼被认出,就像人能在一段话中认出一个电话号码。私钥用其 BEGIN 与 END 装甲行宣告自己,JSON Web Token 有刚性的三段结构,其前两段总是以同样的方式开头,因为它们总是编码同一种 JSON。
两项检验让模式层保持诚实。银行卡号只在通过 Luhn 校验和时才标记——那是每张真实卡内建的算术检验,它把真实卡与数量多得多的、只是长得像卡的长数字——订单、发票、时间戳——区分开来。当形状重叠时,具体的模式胜过通用的模式,一把被赋值给变量的已知服务商密钥只报告一次,冠以它真实的类型,并附上正确的轮换说明。下方第一个来源 GitHub 自己的秘密扫描,在平台尺度上用的正是同样的原理:合作伙伴公布其令牌格式,平台精确匹配它们。本地扫描器是同样的想法,只是运行在它还能帮上忙的最后一刻——你的浏览器里,在你按下发送之前。
扫描器不能捕获什么:熵与上下文
在服务商形状之外,检测就变成概率性的了。通用规则可以寻找赋值——password 或 api_key 这样的名字挨着一个值——并可以要求那个值看起来随机,用香农熵来衡量。这能捕获被赋值给变量的生成密钥,并忽略句子中出现的 password 这个词,但边界是一个穿着数字外衣的判断:强壮的人类口令和弱小的机器密钥活在同一把尺子上,一个拼成短语的真实秘密得分很低,一个没有占位符措辞、看起来随机的构建哈希得分很高。熵能告诉你一个字符串令人意外,却不能告诉你这个字符串是凭据。
上下文是更大的盲区,因为让一个值变得危险的因素大多在值之外。会话 Cookie 只是一串字母和数字,直到你知道它能解锁哪个服务。数据库密码藏在被拆到三个环境变量中的连接串里时看起来无害,一次性验证码只在十分钟内敏感。扫描器也看不穿结构:一个被折到两行日志上的秘密、被 base64 编码进一团数据中的秘密、或被截图进一张图片中的秘密,对文本模式来说是不可见的,无论模式多好。诚实的总结是:扫描器可靠地捕获有文档、形状良好的大多数——而它展示给你的、命中在原地高亮的预览,仍然值得你自己花十秒钟扫一眼,因为你的眼睛知道模式所不知的上下文。
还有一类是扫描器故意看不见的,而它紧挨着陷阱:占位符。文档值——包含 example、test、dummy 或 your- 等词——被排除,使手册和教程不会点亮发现表格。但每一个排除都是一扇门:一个真实密钥碰巧包含其中一个词,或者一个懒惰的真实口令被设置成看起来像占位符的值,都会悄无声息地通过。把排除列表当作噪声控制,永远不要当作保证,也永远不要为了让扫描器保持安静而给真实凭据加上占位符措辞。
规范的伪造示例问题
每个服务商都会公布示例凭据,其中最著名的——围绕 AKIAIOSFODNN7EXAMPLE 构建的 AWS 文档密钥对——形状与真实密钥一模一样,因为一个看起来不真实的示例什么也教不会。于是扫描器面对一个没有好默认值的岔路。标记规范示例,每一份教程、教科书和培训日志都会变成误报,教读者条件反射地无视发现——警报疲劳本身就是一种漏洞。把它们加入白名单,你就等于公布了一份任何扫描器都永不报告的字符串清单,这是在邀请任何想找藏身之处的人:真实密钥不可能等于示例,但围绕真实密钥的笔记、文件名或包装,可以借用示例的伪装。
秘密脱敏工具采取的实用答案是:标记形状,让发现表格开启对话。规范示例自己的密钥里就包含 example 这个词,但它仍被标记为 AWS 密钥,因为一个服务商形状的字符串每次都值得人看一眼——看一眼很便宜,而替代方案是对任何形状相同的东西都悄悄放行。当你编写文档时,也要帮你读者的扫描器和你自己的扫描器一把:使用明显合成的值,在周围的赋值中用占位符措辞标明它们,并且永远不要把真实密钥粘贴进文档再改动几个字符——被部分编辑过的真实密钥仍然是一次凭据泄露,只是多了几个步骤,扫描器也无法把它与真货区分开来。
轮换,不要只是删除——并安全地分享
如果一个真实秘密已经被未脱敏地粘贴出去了,操作的顺序很重要,删除排在最后——如果它还有位置的话。假设这个值已经被复制——被索引、被通知、被截图、被同事引用——并把它当作公开的:先去服务商处轮换该凭据,吊销旧的,使泄露的字符串无论在哪里存活都停止工作。然后才删除或编辑原始消息,那是卫生习惯,而不是修复本身。下方的 OWASP 秘密管理指南和 gitleaks 项目对应着这一课的两半:管理秘密使它们短命且限定范围,以及扫描仓库和历史记录时假设粘贴已经发生。浏览器抓取的配套问题在在发送那份 HAR 文件之前中完整讨论。
安全的分享流程短到可以成为习惯。第一,在本地脱敏,使用一个从不上传原文的工具——扫描属于你的设备,因为把原始文本发送给远程脱敏服务,会在防止泄露的行为中重演泄露。第二,在工单或聊天中分享脱敏副本,它带着排障者需要的一切——端点、用时、错误形状——唯独没有访问权。第三,当对方确实需要凭据本身时,通过秘密管理器或一次性的、单次查看的分享链接发送它,走与讨论分开的渠道,并在工单里说明这一点,以免有人把它粘回来。采用这三步的团队不再依赖警惕性——那是唯一一种终将失败的控制——而是开始依赖一种例行程序;而例行程序是让秘密在疲惫的周五粘贴中和在小心的周一粘贴中一样可靠地远离工单的原因。