邪恶合并:隐藏在Git合并提交中的恶意代码
在一个项目的vite.config.js文件中发现了恶意、混淆的代码。它并非通过拉取请求(PR)引入,而是在冲突解决期间直接出现在合并提交中。GitHub的PR视图仅显示功能分支的差异,忽略了合并提交本身所做的更改。使用git log分析发现,一个合并提交的父提交相同,但文件内容被篡改。
父提交的MD5哈希值匹配:
- 父提交1:
aa82acb0c335430d8300b6cb306dc824(干净) - 父提交2:
aa82acb0c335430d8300b6cb306dc824(干净) - 合并提交:
2a54754defae4d13aab39f256738dbbf(已修改)
在三方合并中,当父文件相同时,Git应产生不变的结果。任何差异都表明在合并过程中进行了手动编辑。
注入与混淆机制
恶意负载被注入到不同模块的两个文件中。贡献者在PR中点击合并按钮时添加了负载。该代码在npm run dev和npm run build期间执行了3.5个月。
混淆技术包括:
- 使用数字种子打乱字符串(如
require、module、constructor)进行重构。 - 使用
Function而非eval以绕过静态分析。 - 使用字符替换表的自定义解码器。
负载不包含URL或IP地址。相反,它:
- 查询TRON钱包的最新交易。
- 提取BSC交易哈希。
- 向BSC节点发起JSON-RPC
eth_getTransactionByHash调用。 - 从交易的
input字段中XOR解码代码。
如果TRON不可用,则使用Aptos作为备用方案。执行使用child_process.spawn,设置detached: true和stdio: 'ignore',并有一个30秒计时器以对抗监视模式检测。
未被发现的原因
- GitHub不在PR中显示合并提交的差异。
git log不显示更改;需要使用git diff <parent1>..<merge>。- 代码通过空格被移动到第200列之后。
- SAST工具因缺乏签名而失败:没有
eval、URL或base64。
这种技术被称为邪恶合并——一种引入父提交中均不存在的更改的合并(gitglossary(7))。Git维护者在2013年描述过它,但检测工具很少。
邪恶合并检测器:自动化扫描工具
一个用Go编写的检测器通过比较预期的三方合并结果(通过共同祖先)与实际合并提交来工作。差异表明手动编辑。
用法:
evilmerge scan /path/to/repo
选项:
--format=sarif用于GitHub代码扫描。- 严重级别:手动冲突解决与无冲突编辑。
集成:
- GitHub Action:
```yaml
- uses: fimskiy/Evil-merge-detector@v1
with:
fail-on: warning
```
- GitHub应用:PR检查和历史回顾扫描。
- GitLab、Bitbucket和自托管设置(预接收钩子)的模板。
缓解措施与工作流漏洞
通过贡献者访问进行攻击:合法提交后,在合并期间注入。PR本身保持干净。
建议:
- 使用线性历史(压缩/变基)。
- 在服务器上实现预接收钩子。
- 在GitHub上:启用“关闭过时审查”以在PR分支更改时要求重新审查。
GitHub确认这是有意设计,未来可能限制。当前措施侧重于预防而非检测。
关键要点
- 邪恶合并是合并提交中手动编辑的官方术语。
- 负载使用区块链(TRON/BSC/Aptos)进行命令与控制,无需URL/IP。
- 基于Go的检测器扫描历史并集成到CI/CD中。
- 在有CI和代码审查的项目中,3.5个月未被发现。
- GitHub暂无更改合并工作流的计划。
— Editorial Team
暂无评论。