mcpField
S
secrets-in-git-history

secrets-in-git-history

github.com/useosint安全571.9k 次安装12 周前更新
代码仓库

关于 secrets-in-git-history

这项安全技能用于检查 Git 仓库中的凭据暴露和组织线索,面向安全调查人员。它强调检查完整历史和所有引用,因为已删除的秘密和提交身份仍可能留存。

主要功能

  1. 01镜像克隆仓库并扫描所有引用
  2. 02使用 TruffleHog 和 gitleaks 检测
  3. 03在历史记录中搜索特定字符串
  4. 04检查提交邮箱与 noreply ID
  5. 05评估发现并排除误报
  6. 06记录提交信息等证据

适用场景

  1. 01查找当前分支已删除的凭据
  2. 02依据提交历史梳理开发者身份
  3. 03调查仓库历史中的基础设施线索

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/useosint/skills --skill secrets-in-git-history

安装后在 Claude Code 中输入/secrets-in-git-history确认已加载。

附带文件reference/git-archaeology.mdreference/high-signal-indicators.md

详细介绍

Git 历史中的机密信息

一个代码库包含三类情报:真实邮箱地址组成的人物名单、组织基础设施地图,以及没人打算公开的凭据存储库。代价高昂的错误,是只扫描工作树。从 HEAD 删除的机密信息会永远留在历史记录中,从分支删除的提交仍可通过分叉网络访问——如果只扫描检出的内容,你搜索的恰恰是机密信息肯定已被删除的唯一位置。

你掌握的信息,以及从哪里着手

你掌握的信息从这里开始原因
组织名称公开的组织成员,然后是他们的个人代码库企业代码库会经过审核;个人代码库则不会
开发者姓名或用户名搜索他们各个代码库中的提交邮箱可以找到其他方法都无法提供的邮箱
提交邮箱在各个平台进行反向搜索;解析任何 noreply ID将不同组织和平台上的账号关联起来
疑似凭据泄露对镜像克隆使用两种扫描器,检查所有 refs只扫描 HEAD 完全偏离了重点
特定字符串——主机名、密钥前缀在所有 refs 上使用 git log -S 拾取搜索查明它何时出现、何时消失
在线域名检查是否暴露了 .git——参见法律说明在获得授权的情况下,从 Web 服务器获取完整历史记录
只有公司网站搜索代码中组织的内部域名找出与组织没有明显关联的代码库

提交邮箱泄露

Git 会在每次提交中记录作者身份,由作者自行填写而非经过验证。这有利也有弊:邮箱可以轻易伪造,但它们也是代码托管平台上的身份与真实邮箱之间最可靠的公开关联。

git clone --mirror https://github.com/example-org/example-repo.git repo.git
git --git-dir=repo.git log --all --format='%an <%ae>' | sort -u

GitHub 的 Web UI 不会显示提交邮箱,因此有人以为它们是隐藏的。其实并非如此。在任意提交 URL 后附加 .patch,即可获得邮件格式的补丁,其 From: 标头中包含作者姓名和邮箱;API 返回的 commit.author.email 也是相同信息。

curl -s https://github.com/example-org/example-repo/commit/<sha>.patch | head -5
curl -s https://api.github.com/repos/example-org/example-repo/commits/<sha> | jq .commit.author

noreply 模式才是真正值得弄清楚的部分。 启用了邮箱隐私保护的用户会以 [email protected] 的形式提交,或使用较新的格式 [email protected]。第二种格式会泄露账号的数字用户 ID。这个 ID 不可变,且无论账号如何改名都会保留,因此旧提交如今仍能定位到当前持有该账号的人:

curl -s https://api.github.com/user/12345678 | jq '.login, .created_at'

这样一来,改名就失去了效果:使用早已弃用的用户名提交的旧记录,会指向此人当前的身份。对目标对象而言,另一种容易忽略的情况是多年提交后才启用邮箱隐私保护——在此日期之前的每次提交都包含真实邮箱地址,因此要从所有历史记录中提取作者列表。

将组织枚举为社交关系图

企业代码库会经过审核,个人代码库则不会,而开发者会在两者中放入相同的基础设施。关键路径是从组织到个人,再到个人账号。

公开组织成员身份需要主动选择公开,因此信息并不完整,所以要通过多种方式交叉验证:https://api.github.com/orgs/<org>/public_members 会列出选择公开身份的成员,而组织所有代码库中的作者列表则能列出所有实际提交过代码的人,包括从未公开身份的人和已经离开的人。

对于每个人,提取其代码库、gists、关注者、关注对象、已加星标和已关注的代码库。星标和关注关系利用得还不够充分:没人会为了隐私整理这些信息,而且相比任何个人资料字段,它们更真实地反映兴趣、雇主、业余项目和职业关系。如果一群开发者都给同一个看起来像内部工具的冷门项目加星标,他们很可能是同事。

公开活动事件只涵盖近期的短暂时间窗口,但公开事件流也以历史归档数据集的形式发布,可将分析范围延伸到数年前。汇总提交时间戳可以推断工作时段和时区。

代码搜索及其实际局限

托管平台的代码搜索可以查找公开仓库中的字符串。搜索只有这个组织才会写的内容:内部主机名、私有软件包名称、云账户 ID、电子邮件域名。公司名称会搜出营销文案;内部域名则会搜出基础设施。

这些局限比语法更重要,因为每一项都是一个隐蔽的盲区。代码搜索只覆盖默认分支——没有历史记录,也没有其他分支——因此,已删除的密钥从一开始就不可能被它发现。并非每个仓库都会被索引:规模特别大的仓库,以及没有独立活动的派生仓库,通常会被排除。通常需要身份验证,而且速率限制很严格。此外,匹配基于词元,因此较长词元中的子字符串可能无法按预期匹配。

将代码搜索视为发现工具,用它找出要克隆的仓库。真正的工作在本地完成。

删除后仍会留存的一切

  • 从 HEAD 中移除的密钥仍留在历史记录中。 删除文件只是一次提交,并非抹除;重写历史也无法触及克隆、派生仓库或缓存;而且几乎没人会在之后轮换密钥,因为他们觉得删掉就算修复了。
  • 派生网络中的提交仍可通过 SHA 访问,即使分支已删除、PR 已关闭但未合并,或派生仓库已被移除。将仓库设为私有无法撤回已进入派生仓库的内容,而强制推送的提交只是悬空,并未消失。
  • Gist 是未列出的,而非私有的——任何拥有 URL 的人都能读取“秘密”Gist,而 URL 会泄露到日志、聊天和工单中。
  • 公开仓库的 CI 日志也是公开的。 每当某个值经过 base64 编码、拼接、URL 编码,或由子进程打印时,对已知密钥值的屏蔽就会失效。Issue 和 PR 附件同样通过不透明 URL 提供服务,即使 Issue 或仓库已不存在,仍然可以访问。

正确扫描

先镜像克隆,再运行两种扫描器——它们的检测器集合和误报特征不同,彼此都不包含对方的全部能力。然后用 pickaxe 搜索它们没有检测器的字符串:内部域名、云账户 ID、配置文件中的主机名。

trufflehog git file://./repo.git
gitleaks detect --source=. -v
git log --all --source -S'internal.example.com' --oneline
git log --all --diff-filter=D --name-only --format='%H %aI %an'

关于验证的一项警告。 TruffleHog 可以使用候选密钥调用提供商的 API,确认该密钥是否有效。这会使用他人的凭据与第三方交互,并在对方的日志中留下记录。在授权评估中,这通常是预期行为。未经授权则不要这样做。

命令见 reference/git-archaeology.md;要搜索的内容以及如何对命中结果进行分类,见 reference/high-signal-indicators.md。

暴露的 .git 目录

将工作树复制到 Web 根目录的部署会连同 .git 一起发布;通过 .git/config、.git/HEAD 和对象存储,可以重建完整历史记录——源代码、凭据、作者身份等,无一例外。

通过搜索索引或现有扫描结果发现存在此类路径,是被动行为。检索它则不是。 下载 .git 目录意味着访问系统并从中获取数据,而配置错误并不构成许可。只有获得书面授权后才能进行;否则应报告此问题。

问题所在

  • 作者身份是自我声明的。 任何人都可以冒用他人名义提交;提交中的邮箱只是声明,并非作者身份的证明。签名提交是例外,应该验证的是签名,而不是字符串。
  • 机器人占据了大量提交记录。 CI、依赖项机器人和合并自动化工具会以虚构身份生成海量提交。在对人员得出任何结论前,先将它们过滤掉。
  • 变基和压缩合并会改写作者信息。 执行合并的人会成为提交者,压缩合并则会将多位作者合并为一位。在记录中找不到某人,不代表此人不参与项目。
  • 时区推断可信度较低。 笔记本电脑会随人移动,时区偏移可以手动设置,CI 提交则使用 UTC。时区只能作为佐证。
  • 测试夹具和文档示例看起来与凭据毫无二致,而供应商示例会传播到成千上万个代码仓库。先搜索,再下结论。
  • 有意公开的密钥不属于泄漏。 可发布且锁定来源站点的浏览器密钥本就应该随产品发布;将其报告为泄漏,会降低你对真实发现的可信度。
  • 同名组织或仿冒域名不是你的目标,而且仅在历史记录中发现的问题往往比当前问题更容易造成影响,因为通常只会移除凭据,却不会轮换凭据。

可信度评级

  • 已确认 —— 身份关联已建立,例如 noreply 数字 ID 能解析到该账户,或同一提交邮箱出现在多个已与此人关联的账户中。凭据暴露的格式有效,周边上下文能确定其作用范围,并且有提交 SHA 和日期。
  • 较可能 —— 某个独特邮箱或用户名在多个代码仓库中反复出现,且提交时间一致;或者真实配置文件(不在测试路径下)中出现了类似凭据的字符串。
  • 未确认 —— 单个未签名提交中的作者字符串;常见用户名;扫描器在测试夹具或文档示例中命中的内容;作用范围未知的凭据。
  • 已排除 —— 文档中注明的占位符、可发布的密钥,或被当作有效泄漏报告的已过期临时凭据。

每项发现都记录代码仓库、提交 SHA、日期和文件路径。SHA 是不可变的引用,行号则不是。

操作示例

目标:梳理一家网站只列出高管的公司的工程团队。

在内部域名 svc.example-corp.net 上搜索代码,找到三个代码仓库,名称都不含该公司的名称。通过镜像克隆这些代码仓库后,发现十四个不同的作者邮箱,其中十一个是公司邮箱,三个是个人邮箱。

其中一位作者使用 [email protected] 进行提交。解析该数字 ID 后,发现它对应的是另一个当前用户名——账户已更名,旧用户名关联着当前用户名没有关联的会议演讲和个人博客。

无果的线索:gitleaks 在一个测试夹具中标记了一个类似 AWS 密钥的字符串,而搜索该字符串后,发现它原样出现在 AWS 自己的文档中。将其排除,并记录排除理由,避免其他人再次报告。

真正的发现是一个 terraform.tfstate 文件,它在两年前的一周内被添加并删除,后从历史记录中恢复。没有有效凭据,但包含四个内部主机名和一个云账户 ID——这些信息可用于 find-hidden-subdomains 的基础设施。

评级:团队名单为较可能,因为提交作者身份是自我声明的,且未经佐证。用户名更改的关联为已确认——数字 ID 不会改变。

线索转向

新选择器前往
提交作者邮箱what-an-email-reveals、what-leaked-about-you
用户名、改名账号、数字 IDhunt-a-handle
从提交记录和 CODEOWNERS 中查找真实姓名find-anyone
从配置中查找内部主机名find-hidden-subdomains、who-owns-this-domain
云账号 ID、存储桶、IP 地址find-exposed-servers、google-like-a-spy
已提交的文档和图片secrets-in-file-metadata
组织结构和供应商技术栈x-ray-a-company
已删除的仓库和已移除的页面read-deleted-pages
出现在公开泄露数据中的凭据find-leaks-in-the-wild
贡献者网络graph-the-network

法律、披露,以及一条不可逾越的规则

绝不要使用你发现的凭据进行身份验证。 不要测试它是否有效,不要进行只读访问,一次也不行。在大多数司法管辖区,这都属于未经授权的访问,违反计算机滥用相关法律;公开发布并不意味着你获得了许可。只能根据格式、上下文和元数据判断凭据是否有效,除此之外一概不要做。参见 ../../ETHICS.md。

看起来仍然有效的凭据意味着你有义务披露。请尽快通过安全联系人、security.txt 渠道或主机的漏洞报告渠道进行报告;提供仓库、提交 SHA 和文件路径;明确指出仅重写历史记录并不足够,必须轮换凭据;只保留经过脱敏的引用;不要发布任何内容。已加入自动化密钥扫描合作计划的主机可能已经撤销该令牌,但这并不免除告知相关人员的义务。

克隆公开仓库属于正常使用;批量克隆和高频率 API 查询会受到速率限制,也可能违反服务条款。无论仓库处于何种状态,提交邮箱都属于 GDPR 及类似法规所定义的个人数据——请仅收集最少数据,并设定保留期限。

评论

支持 Markdown