Highghsa:GHSA-f9j4-qggq-h239

    GitPython: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection') (ghsa:GHSA-f9j4-qggq-h239)

    Published Sources: Hersteller

    What is affected, what to do

    Update GitPython to version 3.2.0.

    Not sure whether and how you are affected? Ask a question about this vulnerability

    Metrics

    —

    Priority

    No value is available for this vulnerability.

    —

    CVSS v3

    No value is available for this vulnerability.

    —

    EPSS score

    No value is available for this vulnerability.

    Show all metrics
    Severity
    High
    Priority (0–325)

    The priority score is a consolidated metric that condenses several individual signals into a single, action-guiding ranking: technical severity (CVSS), exploitation likelihood (EPSS), known-exploited status (KEV) and the context of the affected asset. The aim is to answer the only truly important question in vulnerability management: what first?

    Read in glossary
    30.00 Methodology
    Exploit maturity

    An exploit is code or a technique that specifically uses a concrete vulnerability to force unintended behaviour — such as executing code or escalating privileges. It is the tool that turns a theoretical flaw into a practical attack.

    Read in glossary
    no public PoC known
    KEV status

    The KEV catalogue is a list maintained by the US authority CISA of vulnerabilities for which active exploitation in the wild has been confirmed. It answers not the question „how severe?“ but „is it already being attacked?“ — thereby sharply prioritising patching.

    Read in glossary
    —
    Published
    CWE

    CWE is a catalogue of typical weakness types in software and hardware — such as flawed input validation, unsafe memory handling or poor authentication. Where a CVE names a concrete vulnerability in a concrete product, a CWE describes the underlying class of defect.

    Read in glossary
    CWE-78, CWE-88

    Classification under NIS2 and CRA

    Rule-based classification from exploitation status and severity. It does not replace a case-by-case assessment.

    NIS2

    NIS2 is EU Directive (EU) 2022/2555 to raise the level of cybersecurity across the Union. It substantially broadens obligations compared with the first NIS directive: more sectors, mandatory risk management, staged reporting duties and personal accountability of senior management.

    Read in glossary
    High technical severity: assess and remediate the vulnerability in vulnerability handling under Art. 21(2)(e) NIS2 and document the decision.
    CRA

    The CRA is EU Regulation (EU) 2024/2847 setting horizontal cybersecurity requirements for „products with digital elements“ — from industrial controllers and software to connected consumer devices. For the first time manufacturers must ensure security across the entire product lifecycle, handle vulnerabilities and provide security updates.

    Read in glossary
    From 11.12.2027, Annex I Part II CRA requires manufacturers of affected products with digital elements to handle vulnerabilities without delay and provide security updates.
    ISO/IEC 27001

    ISO/IEC 27001 is the internationally leading standard for an information security management system (ISMS). It describes a risk-based, process-oriented framework to systematically plan, implement, review and improve information security.

    Read in glossary
    Control A.8.8, management of technical vulnerabilities: assess relevance to your systems, plan remediation and keep evidence.

    Description

    Summary

    Remote.pull() in GitPython forwards its positional refspec argument directly to the git pull child process while its unsafe-option guard inspects only keyword arguments. An application that passes attacker-influenced data as the refspec (for example a user-supplied branch or refspec parameter in a web or CI service) can supply an option-shaped value such as --upload-pack=<command>. The value bypasses the unsafe_git_pull_options check, is placed after the -- separator on the git pull command line, and is executed as the upload-pack program for local-path or SSH transports. This yields arbitrary command execution with the privileges of the calling process using the documented default arguments (allow_unsafe_options=False).

    Root Cause

    Remote.pull() is documented to reject dangerous options unless allow_unsafe_options=True, and --upload-pack is explicitly listed as such an option. The guard, however, only screens keyword arguments and never the positional refspec. The check builds its candidate list with Git._option_candidates([], kwargs), passing an empty list in place of the positionals, so the refspec value is never compared against unsafe_git_pull_options = ["--upload-pack"]. The normalized refspec is then forwarded verbatim to the git pull invocation. The leading -- separator that neutralizes an option-shaped refspec for git fetch and git push does not protect git pull, because git pull re-forwards its trailing positionals to an internal git fetch without re-inserting a -- separator, so the positional becomes an option to that internal fetch.

    Affected function: git.remote.Remote.pull
    Source: git/remote.py (guard at the check_unsafe_options call using _option_candidates([], kwargs); sink at the self.repo.git.pull("--", self, refspec, ...) invocation)
    

    Impact

    An attacker who controls the refspec value passed to Remote.pull() achieves arbitrary operating-system command execution as the process invoking GitPython, with no additional privileges and no user interaction, on the default code path. Exploitation requires that the pulled remote uses a transport that runs upload-pack on the local side, namely a local filesystem path or SSH; over HTTPS the --upload-pack value is inert because the server ignores a client-supplied upload-pack program. This transport requirement narrows the population of vulnerable deployments but does not add work for the attacker once a vulnerable target is reached. Working checkouts commonly use local-path or SSH origins, so the exposed population is substantial.

    Proof of Concept

    Environment: GitPython from the affected source tree; system git 2.43.0.
    The pulled origin uses a local filesystem transport (protocol.file.allow=always for git >= 2.38).
    

    Reproduction steps:

    1. Create an origin repository reachable over a local-path transport.
    2. In a consumer repository, register that origin and call Remote.pull() with an attacker-controlled refspec.
    3. Observe that the injected command executes.
    import git
    repo = git.Repo("consumer")                 # has an origin using a local-path or ssh transport
    origin = repo.remote("origin")
    # Attacker-controlled refspec value; default arguments (allow_unsafe_options=False):
    origin.pull(refspec="--upload-pack=touch /tmp/pwned; git-upload-pack")
    
    cmdline: git pull -v -- origin --upload-pack=touch /tmp/pwned; git-upload-pack
    [attack]  marker created (RCE)      : True
    [isolate] upload_pack passed as KWARG blocked : True   (only the positional refspec escapes)
    [sibling] same value via git fetch  : safe            (pull-specific behavior)
    [control] benign refspec            : safe
    

    Attack Chain

    1. Exposure: An application calls Remote.pull(refspec=<value>) on the default path, where <value> is derived from attacker-influenced input such as a branch or refspec request parameter.
    2. Control: The attacker supplies the string --upload-pack=<command>; git-upload-pack. The value passes through Git._unpack_args, which only flattens and stringifies it, preserving the option shape.
    3. Path: The protocol loop calls Git.check_unsafe_protocols on the value, which only matches remote-helper transport URLs (a <transport>:: form) and does not flag an --upload-pack= token.
    4. Guard: The unsafe-option guard calls check_unsafe_options(options=Git._option_candidates([], kwargs), ...). Because positionals are passed as an empty list, the positional refspec is never inspected against unsafe_git_pull_options.
    5. Primitive: The value is forwarded to git pull -v -- <remote> --upload-pack=<command>; git-upload-pack; git pull re-forwards the positional to an internal git fetch without a -- separator, so it is honored as the upload-pack program.
    6. Result: For a local-path or SSH transport, git executes <command> as the upload-pack program, giving arbitrary command execution as the calling process user.

    Bypass Evidence

    The relevant guard is Remote.pull()'s own check_unsafe_options call, present in the current released artifact. It is incomplete: it inspects Git._option_candidates([], kwargs), which only considers keyword arguments, so the positional refspec is never checked. This was confirmed empirically: passing the identical value as the upload_pack= keyword argument raises UnsafeOptionError, while passing it as the positional refspec does not, proving that only the positional escapes the guard.

    The -- separator on the git pull command line was tested as a possible mitigation and refuted: with system git 2.43.0, git pull -- <remote> '--upload-pack=<cmd>' executed the injected command, while the identical value supplied to git fetch -- ... and git push -- ... did not execute, because those subcommands honor the -- terminator whereas git pull re-forwards positionals to an internal fetch without one. The check_unsafe_protocols transport check was also examined and does not flag the --upload-pack= token because it has no <transport>:: form. No prior published advisory for this package addresses Remote.pull, the fetch or pull refspec positional, or --upload-pack injection via a refspec; the closest accepted issue covers the same class (a positional argument escaping a keyword-only guard) in a different function and sink.

    Affected Versions

    • Ecosystem: pip (PyPI)
    • Package: GitPython
    • Confirmed affected range: >= 3.1.30
    • Latest release checked: 3.1.62 (PyPI); source verified against the 3.1.62 release tag
    • Fix status: not fixed in any released version or on the development branch

    The keyword-only pull guard together with the positional refspec sink is present in every released tag from 3.1.30 through the latest release 3.1.62, verified by inspecting the shipped git/remote.py in each tagged artifact. Version 3.1.30 introduced the unsafe_git_pull_options guard; the affected range is stated from that boundary under the incomplete-guard framing. Releases before 3.1.30 forward the refspec positionally with no unsafe-option guard and no -- separator and are also exploitable, but the range is stated conservatively from the guard-introduction boundary. No fixed release exists.

    Suggested Fix

    Enforce the unsafe-option invariant on the positional refspec, not only on keyword arguments. The guard should build its candidate list from the actual forwarded positionals (for example by passing the normalized refspec list into Git._option_candidates alongside kwargs) so that an option-shaped refspec such as --upload-pack=... is rejected with UnsafeOptionError unless allow_unsafe_options=True. The same correction should be verified for any other reference or refspec forwarding path that relies on the keyword-only guard. As an interim mitigation without a code change, callers should validate or reject refspec values that begin with -, avoid passing untrusted data

    Source: GitHub Security Advisories

    Affected products

    The following versions are affected. Older releases of the same product line are also vulnerable unless stated otherwise.

    Affected products
    Affected versionsFixed in
    pipGitPython
    • >= 3.1.30, < 3.2.0
    • 3.2.0

    Weakness classes (CWE)

    • CWE-78Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')

      Base

      The product constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component.

      Entry at MITRE CWE-78
    • CWE-88Improper Neutralization of Argument Delimiters in a Command ('Argument Injection')

      Base

      The product constructs a string for a command to be executed by a separate component in another control sphere, but it does not properly delimit the intended arguments, options, or switches within that command string.

      Entry at MITRE CWE-88