# GitPython: git-config section-name injection enables arbitrary config directives (core.sshCommand RCE)

GitPython writes unescaped submodule names into .git/config section headers, letting a name like `x"] [core] sshCommand=CMD #` inject core.sshCommand for RCE.

- Payout: Research
- Severity: high
- Program: GitPython
- Found by: ranjiGT
- Disclosed: 2026-07-24
- CVE: CVE-2026-69097
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-3rp5-jjmw-4wv2
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-3rp5-jjmw-4wv2
- Tags: area:oss domain:oss impact:rce kind:research platform:ghsa sev:high tech:python technique:incomplete-fix technique:source-review vuln:command-injection vuln:rce

**Target:** GitPython config writer / submodule name handling
**Root cause:** `_assure_config_name_safe` only rejects CR/LF/NUL in section names, but names are serialized into `[%s]` headers without escaping `]`, `[`, `"` or whitespace, so a name can close the header and open a second same-line section.
**Steps:**
1. Install GitPython 3.1.52 (`uv run --with GitPython==3.1.52 python poc.py`).
2. Create a benign bare repo to use as submodule url.
3. Call `git.Repo(parent).create_submodule(name='x"] [core] sshCommand=MARKER_9f3a #', path='sub', url=suburl)`.
4. Inspect `.git/config`: header becomes `[submodule "x"] [core] sshCommand=MARKER_9f3a #"]`.
5. Verify with `git config -f .git/config --get core.sshCommand` returns the injected value.
**How found:** Noticed the sibling newline-injection fix (GHSA-mv93-w799-cj2w) only guards CR/LF/NUL, then reasoned that a same-line `]` inside a section name could break out of the header without any newline, and confirmed the `submodule "<name>"` wrapper conveniently balances the quote needed for the payload.
**Exploitation:** Attacker controls a submodule name either directly via `Repo.create_submodule(name=...)` or indirectly via a hostile repo's `.gitmodules` cloned then `submodule_update(init=True)`. The name is written verbatim into the victim's trusted `.git/config`, injecting `core.sshCommand` (or `alias.*`, `core.pager`, `core.fsmonitor`); git executes the injected command on the next ssh fetch/pull/push or other triggering operation, yielding RCE.
**Escalation:** Chaining the clone-driven path (untrusted `.gitmodules` + `submodule_update`) turns a routine clone into code execution without any direct API misuse.
**Insight:** When a fix targets newline injection in a structured format, check whether the same delimiter can be broken on a single line (e.g. `]` closing a section header); also check whether a wrapper like `submodule "<name>"` supplies the balancing quote an attacker needs.

## Proof of concept (copied exactly from the original report)

**Malicious submodule name payload**

```
evil   = f'x"] [core] sshCommand={MARKER} #'          # closes the header, opens [core]

p_control = parent_repo()
git.Repo(p_control).create_submodule(name=benign, path="docs", url=suburl)
p_exploit = parent_repo()
git.Repo(p_exploit).create_submodule(name=evil, path="sub", url=suburl)
```

**Run command for PoC**

```
uv run --with GitPython==3.1.52 python poc.py
```

