Commit Stomping:操纵Git历史以掩盖真相
Commit Stomping是一种受timestomping启发的技术,这是一种在攻击操作中常用的知名方法,通过操纵文件元数据来隐藏行动的真实时间。在Git中,Commit Stomping涉及更改提交时间戳,以误导观察者关于变更引入的时间。
这不是Git中的错误或漏洞。这是一个功能,当被误用时,可以用来伪装恶意或未经授权的变更,使在审计、调查或代码审查期间建立准确时间线变得更加困难。
我之前在研究RepoMan和伴随的博客文章时写过/研究过这个主题,探讨了受AI影响的代码提交方式,但从未全面探讨过这种独立技术。如果您决定逐字复制本文,请注明出处,而不是盲目复制和抓取。
为什么这种技术重要
Git提交历史通常被依赖作为以下方面的真相来源:
- 事件响应和取证时间线
- 代码审计和合规性审查
- 团队或开源环境中的归属
当提交时间戳被伪造时,确定变更的顺序和来源变得更加困难。这在软件供应链安全、内部威胁和长期审计跟踪的背景下具有重要影响。
Git如何跟踪时间
Git不仅存储发生了什么变化。它还存储关于谁进行了更改以及何时进行的元数据。每个提交包括两个不同的时间戳:
GIT_AUTHOR_DATE
:内容最初编写的日期和时间GIT_COMMITTER_DATE
:提交最终确定到存储库的日期和时间
在大多数工作流中,这些值是相同的。然而,在变基、修改提交或由原始作者以外的其他人执行提交时会出现差异。
变基和修改场景
使用git rebase
或git commit --amend
会重写提交。Git保留GIT_AUTHOR_DATE
但更新GIT_COMMITTER_DATE
以反映更改被重写的时间。
|
|
这会调整提交元数据而不更改创作时间,除非明确覆盖。
多个提交者
在协作工作流中:
- 开发人员可能创建补丁并通过电子邮件发送
- 维护者或审查者可能随后提交它
在这些情况下,作者和提交者可能在身份和时区上不同。如果时间戳未标准化,这种差异可能会使取证分析复杂化。
手动时间戳控制
Git允许您通过环境变量控制两个时间戳:
|
|
这实现了合法的元数据保存,或误用以伪造历史。
支持的时间戳格式
Git为这些环境变量和--date
标志支持各种格式:
1. Git内部格式
|
|
示例:
|
|
这表示2024年11月14日06:13:20 CET。
2. ISO 8601
|
|
3. RFC 2822
|
|
4. 相对时间
|
|
5. 纪元时间
|
|
相当于"1700000000 +0000",除非指定。
使用自定义时间戳提交
Git允许您在创建时为提交分配任意时间戳。这意味着您可以伪造提交日期,使其看起来像是在完全不同的时间点完成的工作。这对于合法原因(如将历史代码移植到新存储库同时保留创作日期)可能有用,但在恶意使用时,它也是Commit Stomping的基础。
要使用回溯时间戳提交,您可以使用环境变量显式设置作者和提交者日期:
|
|
这将创建一个看起来像是在2025年初编写和提交的提交,无论实际制作日期如何。从时间线分析的角度来看,这与在该时间真正制作的提交无法区分,除非通过外部日志或镜像基础设施进行验证。
使用Commit Stomping重写历史
如果已经使用当前或不想要的时间戳进行了提交,您仍然可以返回并更改它。Git强大的历史重写工具使得事后伪造时间戳变得微不足道。这是Commit Stomping变得最有效的地方;重写现有提交以在伪造的历史上下文中植入或隐藏行动。
交互式变基
这种方法非常适合以受控方式更改一个或几个最近的提交:
|
|
这将打开一个编辑器显示最后三个提交。将要更改的提交的pick
更改为edit
。然后:
|
|
这将用您选择的日期替换原始时间戳,同时保持提交内容和哈希更改(因为Git哈希包括元数据)。这非常适合在清理或操纵活动期间进行精确的stomping。
使用git filter-branch
如果您要重写更广泛提交集的时间戳,或者想要针对历史深处的特定哈希,git filter-branch
允许进行脚本化的大规模重写。
|
|
这选择性地更改所有分支中提交abc123的时间戳。您可以扩展逻辑以匹配作者名称、消息或范围。虽然强大,但filter-branch也是资源密集型的,并且没有备份是不可逆的。
考虑改用git filter-repo
。它更快、更灵活,并且对于大型存储库更安全。
自动化Commit Stomping
如果目标是隐藏真实变更顺序或使存储库的时间线看起来嘈杂和随机,您可以自动化整个提交历史的Commit Stomping。这向时间线引入熵,使得识别关键行动发生的时间变得更加困难。
这是一个使用filter-branch和随机Unix时间戳的简单示例:
|
|
此脚本随机设置作者和提交者日期为广泛纪元范围内的值。您可以调整随机性或针对特定范围,以将变更混合到特定时间框架中(例如,将后门放入旧发布周期中)。
这种方法嘈杂、不可逆,并且在无理由进行时是篡改的明显迹象,但在对抗性模拟或攻击操作中,它可以完全破坏基于时间线的调查。
检测Commit Stomping
发现Commit Stomping并不总是直接的,特别是在快速移动的存储库或那些没有一致提交卫生的存储库中。然而,在取证审查、审计或一般源代码控制卫生检查期间,有明显的指标应该引起警惕。
寻找以下内容:
跨多个提交的相同或过于相似的时间戳
具有完全相同GIT_AUTHOR_DATE
和GIT_COMMITTER_DATE
的提交,尤其是连续出现的,表明自动化或手动回溯。当提交似乎反映逻辑上独立的工作部分时,这尤其可疑。
作者和提交者日期不匹配
如果作者日期显著早于提交者日期(例如,几个月或几年),可能表明重写或伪造的历史。虽然在变基或补丁应用期间有些差异是正常的,但大的差距应该被调查。
时间顺序不一致
出现顺序混乱的提交,其中较新的提交具有较早的时间戳,可能是历史操纵的迹象。虽然Git不强制执行严格的时间顺序,但此类提交的模式可以揭示篡改或恶意行动的迹象。
与外部日志的差异
将提交时间戳与您的CI/CD流水线、webhook事件日志或镜像存储库进行比较。如果一个提交声称来自1月,但构建系统在4月首次看到它,那是一个危险信号。
不寻常的历史回溯集群
如果一个贡献者突然开始提交大量具有遥远过去时间戳的代码,可能是试图插入或埋葬变更而不引起注意。
在受监管或高信任环境中定期审计提交元数据是供应链完整性的重要部分。
防御Commit Stomping
Git是一个围绕灵活性和离线工作流设计的分布式系统。这种灵活性包括让用户为提交分配任意时间戳,这也意味着Git不验证提供的日期是否真实。如果您希望加固存储库以防止时间线操纵,以下措施将有所帮助:
要求和验证签名提交
GPG或SSH签名提交提供了一种验证提交者身份的方法。虽然这不直接防止时间戳篡改,但它增加了一层问责制,并有助于检测未经授权的重写尝试。将签名检查集成到您的CI流水线中,并在受保护分支上拒绝未签名的提交。
在摄取时捕获元数据
在变更进入您的生态系统时记录提交事件 - 这可能是CI/CD流水线中的时间戳记录、Git服务器中的审计跟踪或存储在日志聚合器中的webhook有效负载。这些摄取日志为变更实际发生的时间创建了一个外部参考点。
使用具有不可变存储的镜像存储库
将提交推送到安全位置托管的只读镜像。此镜像充当时间线的独立副本。如果有人在主存储库中重写历史,您可以与镜像比较以识别差异。
锁定历史重写
在主分支或受保护分支上禁用强制推送。这不会阻止某人在分支或个人分支中重写历史,但它防止未经授权的时间线变更在未经通知的情况下进入您的关键分支。
监控异常模式
使用提交分析工具或基本脚本来监视时间戳异常,例如大的日期差距、提交历史中的向后跳跃或旧日期提交的突发。将其与发布或合规周期期间的定期手动审查配对。
最终,防御Commit Stomping是关于在开发过程周围建立信任和验证层。Git给了您绳子 - 团队是用它安全构建还是自挂,取决于这些控制的实施方式。
最后思考
Commit Stomping不是传统意义上的漏洞 - 没有漏洞利用,没有CVE,也没有补丁到来。这是一个真正的案例,它不是错误,而是Git分布式和基于信任的设计的一个功能,可以转变为欺骗技术。问题不是Git坏了,而是它隐含地信任用户。这种信任可能被滥用。
手动设置或重写提交时间戳的能力本身并不坏。它通常用于实际原因,如导入旧项目时保留日期、与外部系统对齐贡献或整理混乱的提交历史。但在错误的手中,它可能被滥用来重写过去,掩盖变更真正进行的时间,隐藏恶意编辑,或误导任何试图追踪发生了什么以及何时发生的人。
在一个源代码控制是安全边界一部分的世界中,假设Git日志的完整性不再足够。重要的是将版本历史视为可变的,除非证明 otherwise,并用提供外部验证的控制包围Git:CI/CD日志记录、签名提交、不可变镜像和行为监控。
如果您正在模拟高级对手、响应内部威胁、在事件期间审查代码历史或构建加固的开发流水线,操纵时间戳的能力必须是您威胁模型的一部分,并且意识到各种攻击的影响是关键。如果您不寻找它,您就不会看到它 - 如果您不防御它,您就在取证跟踪中留下了一个安静的缺口。
归根结底,Git讲述了一个故事。Commit Stomping让您重写该故事的时间线,并沿途更改一些关键细节。
Git链接:https://github.com/ZephrFish/CommitStomping-Info