VAITP Dataset

← Back to the dataset

CVE-2026-52726

Path traversal in Dulwich submodules allows RCE via a malicious repo.

  • CVSS 7.5
  • CWE-22
  • Input Validation and Sanitization
  • Remote

Dulwich is a pure-Python implementation of the Git file formats and protocols. Starting in version 0.23.2 and prior to version 1.2.5, `dulwich.porcelain.submodule_update`, and by extension `porcelain.clone(…, recurse_submodules=True)`, materializes attacker-controlled submodule paths from a crafted upstream repository without path validation. A malicious `.gitmodules` plus a matching tree gitlink whose `path` is `.git/hooks` (or any other directory inside the parent repository's `.git` directory) causes the attacker's submodule tree contents to be written directly into the victim's `.git/hooks/` directory, preserving executable mode bits. The dropped executables are then run by any subsequent `git` or `dulwich` command that invokes the matching hook, resulting in arbitrary code execution. This is the dulwich equivalent of the upstream Git fixes for CVE-2024-32002 / CVE-2024-32004, which were never propagated into dulwich's separately implemented submodule porcelain. Version 1.2.5 patches the issue.

CVSS base score
7.5
Published
2026-06-10
OWASP
A08 Software and Data Integrity Failures
Orthogonal defect classification
Checking
Code defect classification
Missing Check
Category
Input Validation and Sanitization
Subcategory
Path Traversal
Accessibility scope
Remote
Impact
Arbitrary Code Execution
Affected component
Dulwich
Fixed by upgrading
Yes

Solution

Upgrade Dulwich to version 1.2.5 or later.

Vulnerable code sample

import os
import tempfile
from dulwich import porcelain

# This code, when run with a vulnerable version of dulwich (e.g., 0.23.2 through 1.2.4),
# is susceptible to arbitrary code execution. It demonstrates the client-side
# action that triggers the vulnerability.

# An attacker would host a repository at this URL. The repository would be
# specially crafted with a .gitmodules file pointing a submodule path
# to a sensitive location like `.git/hooks`.
malicious_repo_url = "https://example.com/malicious-repo-cve-2026-52726.git"

# A temporary directory where the victim will clone the repository.
target_path = os.path.join(tempfile.gettempdir(), "vulnerable-clone-demo")

# The vulnerable operation.
# The `recurse_submodules=True` flag triggers the submodule update process.
# In vulnerable versions, dulwich does not validate the submodule's `path`
# specified in the `.gitmodules` file.
# An attacker can set the path to ".git/hooks", causing the contents of their
# malicious submodule (e.g., an executable "post-commit" script) to be written
# directly into the victim's local .git/hooks/ directory.
# Any subsequent git command that triggers this hook would then execute the
# attacker's code.
try:
    porcelain.clone(
        malicious_repo_url,
        target_path,
        recurse_submodules=True
    )
    print(f"Repository cloned to {target_path}")
    print("If the remote repository was malicious, a hook may have been installed.")
except Exception as e:
    # This exception is expected because the URL is a placeholder.
    # In a real attack, the attacker would provide a working URL.
    print(f"Clone failed (as expected with a placeholder URL): {e}")

Patched code sample

def validate_submodule_path(path: bytes):
    """
    Validate that a submodule path from a .gitmodules file is not inside .git/.

    This logic represents the fix for the vulnerability. Prior to the fix, a
    maliciously crafted submodule path (e.g., b".git/hooks/post-commit") was
    not validated, allowing an attacker to write executable hook files into
    the victim's .git directory.

    This validation function is called on the submodule path before it is used,
    raising an exception if the path is unsafe.
    """
    # Normalize path to use forward slashes and split into components.
    components = path.replace(b"\\", b"/").split(b"/")

    # FIX: Block paths that begin with ".git".
    # This prevents writing directly into the .git directory.
    if components and components[0] == b".git":
        raise ValueError(f"submodule path is inside .git: {path!r}")

    # FIX: Block paths that contain ".git" as an intermediate directory.
    # This prevents attacks like "src/.git/modules" on case-insensitive filesystems.
    if b".git" in components:
        raise ValueError(f"submodule path contains .git component: {path!r}")

Payload

# File: .gitmodules (in the parent repository)
[submodule "evil"]
    path = .git/hooks
    url = ./malicious-submodule

# File: post-checkout (in the 'malicious-submodule' repository, marked as executable)
#!/bin/sh
echo "PWNED by CVE-2026-52726: Arbitrary code executed."
touch /tmp/pwned_by_dulwich

Cite this entry

@misc{vaitp:cve202652726,
  title        = {{Path traversal in Dulwich submodules allows RCE via a malicious repo.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-52726},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-52726/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.

The supreme art of war is to subdue the enemy without fighting.

Sun Tzu – “The Art of War”

:: Shaping the future through research and ingenuity ::