diff --git a/src/network-services-pentesting/pentesting-web/git.md b/src/network-services-pentesting/pentesting-web/git.md index e971f034c7b..92702cf36fb 100644 --- a/src/network-services-pentesting/pentesting-web/git.md +++ b/src/network-services-pentesting/pentesting-web/git.md @@ -41,24 +41,103 @@ ls .git/hooks * **TruffleHog v3+**: entropy+regex with automatic Git history traversal. `trufflehog git file://$PWD --only-verified --json > secrets.json` * **Gitleaks** (v8+): fast regex ruleset, can scan unpacked tree or full history. `gitleaks detect -v --source . --report-format json --report-path gitleaks.json` -### Server-side Git integration RCE via `hooksPath` override +### Server-side Git integrations as an attack surface -Modern web apps that integrate Git repos sometimes **rewrite `.git/config` using user-controlled identifiers**. If those identifiers are concatenated into `hooksPath`, you can redirect Git hooks to an attacker-controlled directory and execute arbitrary code when the server runs native Git (e.g., `git commit`). Key steps: +If the application **clones attacker-controlled repositories** and later runs `git` operations from a web/API action (`status`, `diff`, `checkout`, `pull`, `merge`, `commit`, dependency sync, package install, branch switch...), treat Git itself as part of the **server-side attack surface**. -* **Path traversal in `hooksPath`**: if a repo name/dependency name is copied into `hooksPath`, inject `../../..` to escape the intended hooks directory and point to a writable location. This is effectively a [path traversal](../../pentesting-web/file-inclusion/README.md) in Git config. -* **Force the target directory to exist**: when the application performs server-side clones, abuse clone destination controls (e.g., a `ref`/branch/path parameter) to make it clone into `../../git_hooks` or a similar traversal path so intermediate folders are created for you. +### Git config / hook abuse when the backend uses the native Git CLI + +If you can **write or append** to `.git/config`, several directives can turn a low-impact file write into **server-side command execution** during later Git actions: + +| Directive | Common trigger | Abuse | +| --- | --- | --- | +| `core.fsmonitor` | `git status`, `git diff` | Executes an external helper on common working-tree operations | +| `core.sshCommand` / `core.gitProxy` | `git fetch`, `git push`, `git clone` | Replaces the transport command | +| `credential.helper` | Authenticated Git operations | Runs a shell helper for credential lookup/storage | +| `filter..clean` / `filter..smudge` | `git add`, `git checkout`, `git clone` | Executes external clean/smudge filters | +| `diff.external` | `git diff` | Executes an external diff tool | +| `core.hooksPath` | commit / checkout / merge / push | Redirects hook execution to an attacker-controlled directory | + +* **Append-only writes can still work**: Git accepts duplicate INI sections and the **last value may win**, so appending a second `[core]` section can be enough. +* **Direct hook write** also works if you can plant files inside `.git/hooks/` such as `post-checkout`, `post-merge`, or `pre-commit`. +* This is mainly a **native Git CLI** primitive. **JGit/libgit2/go-git** usually do **not** execute the same hooks/config helpers, although they can still be exploitable through path and symlink handling. + +### `hooksPath` overwrite via path traversal + +Modern web apps sometimes **regenerate `.git/config` from user-controlled repo names, dependency names, refs, or workspace identifiers**. If one value lands inside `core.hooksPath` and another one controls **where the generated config is written**, you can chain both into RCE: + +* **Path traversal in `hooksPath`**: if a repo/dependency name is copied into `hooksPath`, inject `../../..` to escape the intended hooks directory and point to a writable location. This is effectively a [path traversal](../../pentesting-web/file-inclusion/README.md) in Git config. +* **Overwrite another repo's `.git/config`**: if `ref` / branch / destination path controls where generated Git metadata is written, traverse into another workspace and replace its config. +* **Force intermediate directories to exist**: abuse clone destination controls so the backend creates paths such as `../../git_hooks` for you. * **Ship executable hooks**: set the executable bit inside Git metadata so every clone writes the hook with mode `100755`: ```bash git update-index --chmod=+x pre-commit ``` - Add your payload (reverse shell, file dropper, etc.) to `pre-commit`/`post-commit` in that repo. -* **Find a native Git code path**: libraries like **JGit** ignore hooks. Hunt for deployment flows/flags that fall back to system Git (e.g., forcing deploy-with-attached-repo parameters) so hooks will actually run. -* **Race the config rewrite**: if the app sanitizes `.git/config` right before running Git, spam the endpoint that writes your malicious `hooksPath` while triggering the Git action to win a [race condition](../../pentesting-web/race-condition.md) and get your hook executed. +* **Find a native Git code path**: libraries like **JGit** ignore hooks. Hunt for features that fall back to system Git so hooks will actually run. +* **Race the config rewrite**: if the app restores `.git/config` right before invoking Git, keep overwriting it while triggering the Git action to win a [race condition](../../pentesting-web/race-condition.md). + +### Buried bare repositories & repo-detection abuse + +Git discovers repositories by walking upward and looking for `HEAD`, `config`, `objects/`, and `refs/`. Therefore, an attacker can **bury a bare repository inside a normal repository subdirectory** and wait until the service runs Git from there. + +1. Create a nested bare repo. +2. Change `bare = true` to `bare = false`. +3. Add `core.worktree` and an execution directive such as `core.fsmonitor`. +4. If the service later `cd`s into that folder and runs `git status` / `git diff`, Git loads the buried config and executes your helper. + +Also check whether deleting/corrupting `.git/HEAD` could make Git stop recognizing the intended repository and **fall through** to a planted bare repo in the same or parent directory. + +### Git argument injection and unsafe shell construction + +When untrusted **filenames, branch names, refs, or paths** are passed to Git without `--`, values starting with `-` / `--` may be parsed as **options** instead of positional arguments. + +```bash +# Vulnerable + git checkout $branch + git rm $path + +# Safer + git checkout -- "$branch" + git rm -- "$path" +``` + +Interesting primitives to test: + +* `--pathspec-from-file=` with `git rm` to make Git read pathspecs from an arbitrary file and leak content via errors. +* Any workflow that joins argv into a **shell string** instead of using a real argument array. +* Cases where the backend safely escapes Git arguments but still embeds an **unescaped working directory** in a shell command like `cd #{working_directory} && git ...`; if you can control the directory name, shell syntax such as `$(id)` may execute before Git starts. + +### Symlink-based repository escape + +Git stores symlinks as blob targets. If checkout materializes **real symlinks** and the web UI/API **follows symlinks**, a repository-scoped file read/write primitive can become **filesystem escape**: + +```text +repo/ + link -> ../../../etc/ +``` + +Then `repo/link/passwd` may resolve to `/etc/passwd`, and writes through that path may escape the repository boundary too. + +Extra pivot points: + +* **Blacklist bypass via repo-internal symlinks**: if the app blocks direct `.git/` reads with a prefix check, look for alternative paths such as `node_modules/pkg/.git/config` that resolve back to the real repo root through symlinks. +* **JGit `core.symlinks` abuse**: even if JGit ignores classic config-to-RCE directives, writing `symlinks = true` in `.git/config` can make the next pull/checkout materialize attacker-controlled symlinks, exposing broader filesystem paths or even other tenants' repositories if storage is shared. + +### Quick checklist when you find a Git-backed file primitive + +* **File read**: inspect `.git/config` for credentials, remote URLs, internal paths, repo layout, and deployment metadata. +* **File write / append**: target `.git/config`, `.git/hooks/*`, or alternate repo markers (`HEAD`, `config`, `objects/`, `refs/`) to escalate into command execution or repo confusion. +* **File delete**: test whether removing `.git/HEAD` changes which repository Git discovers. +* **Path traversal / arbitrary folder creation**: look for generated clone destinations, dependency sync paths, worktrees, hook paths, and package install directories. +* **Package installation features**: local npm dependencies (`"pkg": "./"`, `file:`) may create symlinks under `node_modules/` that help bypass path blacklists and reach `.git/config` indirectly. ## References - [holly-hacker/git-dumper – parallel fast /.git dumper](https://github.com/holly-hacker/git-dumper) - [Ebryx/GitDump](https://github.com/Ebryx/GitDump) +- [The gift that keeps giving: Exploiting Git Integrations in Cloud Services](https://nopnop.pro/2026/06/17/exploiting-git-integrations-in-cloud-services/) +- [Argument Injection Vectors (SonarSource)](https://sonarsource.github.io/argument-injection-vectors/) +- [Git buried bare repos and fsmonitor abuses (justinsteven)](https://github.com/justinsteven/advisories/blob/main/2022_git_buried_bare_repos_and_fsmonitor_various_abuses.md) - [LookOut: RCE and internal access on Looker (Tenable)](https://www.tenable.com/blog/google-looker-vulnerabilities-rce-internal-access-lookout) {{#include ../../banners/hacktricks-training.md}}