| name | git-integration-exploitation |
| description | Exploit git integrations in SaaS and cloud services -- argument injection per git subcommand, JGit vs native git attack path selection, .git/config append-only takeover, error-based file read via --pathspec-from-file, and symlink-based filesystem escape. Use when target has git-backed features like web IDEs, CI/CD pipelines, deployment from repo, LookML/Dataform-style config, or any feature that clones/pulls/commits on the server side. |
Git Integration Exploitation
Systematic audit methodology for SaaS services that integrate git server-side. The attack surface is not git itself -- it is the service's assumptions about what git operations are safe.
When to Use
- Target has a web IDE, notebook, or config editor backed by git
- CI/CD pipeline clones user-controlled repos
- Deployment feature pulls from git (Heroku-style, Cloud Build, Dataform)
- Service accepts git URLs as input (import, migration, dependency resolution)
.git/ directory or git CLI invocation visible in errors, headers, or source
Recon: Identify the Git Implementation
The first decision point. Different implementations have different exploitable surfaces.
| Implementation | Hooks | fsmonitor | symlinks | Argument injection |
|---|
| Native git CLI | Yes | Yes | Yes (Linux/macOS default) | Yes |
| JGit (Java) | No | No | Only if core.symlinks=true in config | No (API-based) |
| libgit2 / go-git | No | No | Varies | No (API-based) |
How to fingerprint:
- Error messages: Java stack traces (JGit), C/Go traces (libgit2/go-git), shell errors (native)
- Timing: native git shells out (slower cold start), JGit is in-process
- Behavior: create a repo with a
post-checkout hook -- if it fires, native git
Rule: If JGit, skip hooks/fsmonitor -- pivot to symlinks or config-based file read/write. If native git, hooks and fsmonitor are the fastest path to RCE.
Attack Primitives
1. Argument Injection (native git only)
The service constructs a git CLI command with user-controlled input (branch name, file path, remote URL). If the input starts with -, git interprets it as an option.
The surface is per-command -- enumerate which git subcommand the service calls, then check that command's dangerous flags:
| Git command | Dangerous flag | Effect |
|---|
git clone / git fetch | --upload-pack=<cmd> | Arbitrary command execution |
git rm | --pathspec-from-file=<path> | Read arbitrary file (contents leak via error) |
git diff | --output=<path> | Write diff output to arbitrary path |
git log | --output=<path> | Write log output to arbitrary path |
git apply | --directory=<path> | Control patch application target directory |
git push | --receive-pack=<cmd> | Arbitrary command execution on remote |
Error-based file read via --pathspec-from-file: Create a file or folder named --pathspec-from-file=/etc/passwd. When the service runs git rm on it, git reads the target file, tries to parse each line as a pathspec, and dumps non-matching lines in error output. Works best on text files with non-path characters. Binary files or files with path-like content may not leak meaningfully. Test with /etc/hostname (short, predictable) before targeting larger files.
2. Config Append-Only Takeover
Git's .git/config uses INI format where duplicate [core] sections are merged -- last value wins (see config-file-parsing-bugs skill for the general INI parser pattern). Append-only write access to .git/config lets you override any config key, including core.fsmonitor (RCE on next git status) and core.symlinks (enable symlink following on JGit).
Where to look: Any API that writes to the repo working directory without path sanitization (WriteFile, file upload, template generation). Test: write to .git/config directly -- many services forget to block the .git/ prefix.
3. Symlink Filesystem Escape
When core.symlinks = true (default on Linux/macOS for native git), git creates real filesystem symlinks during checkout. A repo containing a symlink to / exposes the entire filesystem through the service's file browser. For symlink attacks via archive upload (non-git), see archive-path-traversal skill.
JGit twist: JGit defaults core.symlinks = false. But if you can write to .git/config (primitive #2), add symlinks = true under [core]. Next checkout (triggered by pull, merge, or reset) creates real symlinks.
Cross-tenant escalation: In multi-tenant cloud services, filesystem escape via symlink often reaches other tenants' repo directories on shared infrastructure.
4. Embedded Bare Repository
Push a repo containing a subdirectory structured as a bare git repo (has HEAD, config, objects/, refs/). If the service runs any git command from within that subdirectory, git discovers the embedded config.
Weaponize: set bare = false + core.worktree = . + core.fsmonitor = <cmd> in the embedded config. Any git status from that directory triggers execution.
5. TOCTOU on Config Regeneration
Services that regenerate .git/config before each git operation (as a safety measure) create a race window. Concurrent requests -- one writing the malicious config, one triggering the git operation -- can win the race.
Test: Use an intruder/fuzzer with two request groups running in parallel. Usually wins within 5-20 attempts.
Audit Checklist
1. [ ] Does the service expose git-backed features? (IDE, deploy, import, CI)
2. [ ] Which git implementation? (native / JGit / libgit2 / go-git)
3. [ ] Can you write to .git/config? (file API, upload, template injection)
4. [ ] Which git subcommands does the service call? (trigger commit/push/pull/diff,
grep responses for subcommand names, review client JS for action/command params)
5. [ ] Is user input used in git CLI arguments? (branch, path, remote URL, ref name)
6. [ ] Does the file browser follow symlinks? (create symlink in repo, check UI)
7. [ ] Does the service regenerate config before operations? (race condition window)
8. [ ] Multi-tenant? (filesystem escape = cross-tenant = critical)
References