1. Overview

A path traversal vulnerability exists in the Apache MINA SSHD sshd-git module, which provides Git-over-SSH server functionality. The module fails to validate user-supplied repository paths for directory traversal sequences when handling git-upload-pack and git-receive-pack commands. An SSH-authenticated attacker can supply paths containing ../ to escape the configured Git root directory and access arbitrary Git repositories on the server filesystem, exfiltrating source code, configuration, and secrets. The vulnerability also permits unauthorized writes to repositories via git-receive-pack (push). Apache addressed this vulnerability in MINA SSHD 2.18.0 and 3.0.0-M4, released May 2026.

2. Vulnerability Type

FieldValue
Primary CWECWE-22: Improper Limitation of a Pathname to a Restricted Directory (‘Path Traversal’)
Related CWECWE-23: Relative Path Traversal

3. Severity

CVSS 3.1 (from Apache Advisory)

FieldValue
Score7.1 (High)
VectorCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:N

Our Assessment (CVSS 4.0)

Metric GroupMetricValue
Base — ExploitabilityAttack Vector (AV)Network
Attack Complexity (AC)Low
Attack Requirements (AT)Present
Privileges Required (PR)Low
User Interaction (UI)None
Base — Vulnerable SystemConfidentiality (VC)High
Integrity (VI)Low
Availability (VA)None
Base — Subsequent SystemConfidentiality (SC)None
Integrity (SI)None
Availability (SA)None
ThreatExploit Maturity (E)Proof-of-Concept
FieldValue
CVSS 4.0 Score6.3 (Medium)
CVSS 4.0 VectorCVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:H/VI:L/VA:N/SC:N/SI:N/SA:N/E:P

Scoring rationale:

  • AT:P (Present): The application must explicitly use the sshd-git module — applications using only sshd-core or sshd-sftp are not affected. This is a non-default, opt-in module.
  • PR:L (Low): Requires valid SSH credentials to authenticate to the server.
  • VC:H (High): Complete read access to any git repository on the filesystem reachable by the server process.
  • VI:L (Low): git-receive-pack (push) allows unauthorized writes, but only to valid git repositories.

4. Affected Products

Affected Versions

ProductVersion RangeFixed VersionCPE 2.3
Apache MINA SSHD sshd-git2.0.0 - 2.17.12.18.0cpe:2.3:a:apache:sshd:*:*:*:*:*:*:*:*
Apache MINA SSHD sshd-git3.0.0-M1 - 3.0.0-M33.0.0-M4cpe:2.3:a:apache:sshd:3.0.0:milestone1-3:*:*:*:*:*:*

Tested Environment (Vulnerable)

FieldValue
ProductApache MINA SSHD
Modulesshd-git
Version2.17.1
Maven Coordinatesorg.apache.sshd:sshd-git:2.17.1
Git Tagsshd-2.17.1

Tested Environment (Patched)

FieldValue
Version2.18.0
Maven Coordinatesorg.apache.sshd:sshd-git:2.18.0
Git Tagsshd-2.18.0
Fix Commitdb0567b33 (“Improve git access”)

5. Root Cause Analysis

5a. Detailed Description

The vulnerability resides in two locations within the sshd-git module:

  1. GitPackCommand.resolveRootDirectory() — handles git-upload-pack and git-receive-pack SSH commands
  2. EmbeddedCommandRunner.execute() — handles embedded JGit commands via the --git-dir option

When an SSH client executes a git command (e.g., git clone ssh://user@host/path/to/repo.git), the server receives the repository path as an argument to git-upload-pack. The GitPackCommand.resolveRootDirectory() method resolves this path against the configured server root directory.

Vulnerable code — GitPackCommand.resolveRootDirectory() (sshd-git <= 2.17.1):

protected Path resolveRootDirectory(String command, String[] args) throws IOException {
    GitLocationResolver resolver = getGitLocationResolver();
    Path rootDir = resolver.resolveRootDirectory(command, args, getServerSession(), getFileSystem());
    ValidateUtils.checkState(rootDir != null, "No root directory provided for %s command", command);

    String pathArg = args[1];                    // User-supplied path from SSH command
    int len = GenericUtils.length(pathArg);
    // Strip any leading path separator since we use relative to root
    if ((len > 0) && (pathArg.charAt(0) == '/')) {
        pathArg = pathArg.substring(1);          // Only strips leading '/'
    }

    ValidateUtils.hasContent(pathArg, "No %s command sub-path specified", args[0]);
    return rootDir.resolve(pathArg);             // <-- PATH TRAVERSAL: no validation!
}

The only sanitization is stripping a leading / character. The path is then passed directly to java.nio.file.Path.resolve(), which processes .. components, allowing the resolved path to escape the configured root directory.

Vulnerable code — EmbeddedCommandRunner.execute() (sshd-git <= 2.17.1):

// Line 131 — same issue, gitdir comes from parsed --git-dir option
gitdir = Objects.toString(rootDir.resolve(gitdir));

Attack flow:

StepValueDescription
1Client sendsgit-upload-pack '/../secret/secret.git'
2Server receivespathArg = "/../secret/secret.git"
3Strip leading /pathArg = "../secret/secret.git"
4rootDir.resolve()/opt/git-root + ../secret/secret.git = /opt/secret/secret.git
5ResultServer opens repository outside the configured root

Multiple traversal patterns are possible:

Input PathAfter ProcessingEscapes Root?
/repos/allowed.gitrepos/allowed.gitNo (legitimate)
/../secret/repo.git../secret/repo.gitYES
/repos/../../secret/repo.gitrepos/../../secret/repo.gitYES
/./../../other.git./../../other.gitYES

5b. Vulnerable Code and Call Flow

The SSH protocol flow for git operations:

SSH Client                          SSHD Server (sshd-git)
    |                                       |
    |-- SSH Auth (user/pass or key) ------->|
    |<---------- Auth Success --------------|
    |                                       |
    |-- exec: git-upload-pack '/../../x' -->|
    |                                       |
    |    GitPackCommand.run()               |
    |      └─ resolveRootDirectory()        |
    |           ├─ resolver.resolveRootDirectory()  → /opt/git-root
    |           ├─ strip leading '/'                → "../../x"
    |           └─ rootDir.resolve("../../x")       → /opt/../x = /x  ← TRAVERSAL
    |                                       |
    |<------- git pack data (refs) ---------|
    |<------- git objects (file data) ------|

The GitPackCommand.run() method at GitPackCommand.java calls resolveRootDirectory(), then opens the resulting path as a JGit Repository. If a valid git repository exists at the traversed path, JGit serves its contents to the client — including all files, commits, branches, and history.

5c. Fix (Patched Version)

The fix in commit db0567b33 (“Improve git access”, 2026-05-22) adds a centralized path validation method resolveGitRepo() in AbstractGitCommand:

public static Path resolveGitRepo(Path serverRoot, String pathArg) throws IOException {
    String originalPath = pathArg;
    // Strip leading path separator
    if ((len > 0) && (pathArg.charAt(0) == '/')) {
        pathArg = pathArg.substring(1);
    }

    Path gitRepoPath = Paths.get(pathArg);

    // CHECK 1: Reject absolute paths
    if (gitRepoPath.isAbsolute()) {
        throw new IOException("Absolute git repository path not allowed: " + originalPath);
    }

    // CHECK 2: Normalize to resolve . and .. components
    gitRepoPath = gitRepoPath.normalize();

    // CHECK 3: Reject empty paths (root directory access)
    int componentCount = gitRepoPath.getNameCount();
    if (componentCount == 0 || gitRepoPath.toString().isEmpty()) {
        throw new IOException("Invalid git repository path " + originalPath);
    }

    // CHECK 4: Reject any remaining . or .. in components
    for (int i = 0; i < componentCount; i++) {
        String component = gitRepoPath.getName(i).toString();
        if (".".equals(component) || "..".equals(component)) {
            throw new IOException("Invalid git repository path " + originalPath);
        }
    }

    return serverRoot.resolve(gitRepoPath);
}

Both GitPackCommand.resolveRootDirectory() and EmbeddedCommandRunner.execute() are updated to use this centralized method:

VulnerablePatched
return rootDir.resolve(pathArg);return resolveGitRepo(rootDir, pathArg);
gitdir = Objects.toString(rootDir.resolve(gitdir));gitdir = Objects.toString(AbstractGitCommand.resolveGitRepo(rootDir, gitdir));

The fix implements defense in depth with four validation layers: absolute path rejection, path normalization, empty path rejection, and component-level ./.. checks.

Five new tests were added to GitPackCommandTest.java:

  • parentCheck() — blocks /../other/ traversal
  • parentCheck2() — blocks /foo/../../other/ traversal
  • symLinkAllowed() — confirms legitimate symlinks within root still work
  • gitRootAccessDenied() — blocks access to root directory via /foo/../
  • giCannotEscapeRoot() — blocks /./foo/../../ escape

5d. Impact

The vulnerability allows any SSH-authenticated user to read the complete contents and history of any Git repository accessible to the server process on the filesystem. This includes private repositories, infrastructure-as-code repos containing secrets, configuration repos with credentials, and any other git repository outside the intended root. The git-receive-pack handler has the same vulnerability, allowing unauthorized pushes (writes) to repositories outside the root, though this is constrained by filesystem permissions.

The practical impact is significant in multi-tenant Git hosting environments where the sshd-git module enforces tenant isolation through the configured root directory. An attacker with legitimate access to their own repositories can escape their designated root and access other tenants’ repositories. The requirement for SSH authentication (PR:L) reduces the attack surface, and the non-default nature of the sshd-git module (AT:P) limits the number of affected deployments.

6. Proof-of-Concept

6a. PoC Code

Download poc_cve_2026_48827.py (enterprise email verification required)

FileDescription
poc_cve_2026_48827.pyPython exploit — connects via SSH and performs full git smart protocol handshake to exfiltrate file contents from traversed repository

6b. Reproduce Instructions

Prerequisites:

  • Java 11+ and Maven 3.6+ (for the vulnerable server container)
  • Python 3 with paramiko installed (for the exploit)

Step 1: Start the vulnerable server (Podman/Docker):

podman build -t cve-2026-48827-vuln -f Dockerfile .
podman run -d --name vuln-sshd-git -p 2222:2222 cve-2026-48827-vuln

This creates:

  • workdir/git-root/repos/allowed.git — legitimate repo (inside root)
  • workdir/secret/secret.git — sensitive repo (OUTSIDE root)
  • SSHD server on port 2222 (user: testuser, pass: testpass)

Step 2: Run the exploit:

python3 poc_cve_2026_48827.py --host 10.0.0.1 --port 2222 \
    --user testuser --password testpass \
    --path "/../secret/secret.git"

Expected output on vulnerable server (sshd-git 2.17.1):

[+] SSH authenticated as testuser
[+] Got 2 refs from outside-root repo:
    f3a74159f7e5  HEAD
    f3a74159f7e5  refs/heads/master

[+] Received 316 bytes of pack data
[+] Extracted 3 git objects

[!!!] EXFILTRATED BLOB (89 bytes):
============================================================
THIS IS CONFIDENTIAL DATA
API_KEY=sk-REDACTED-super-secret-key-12345
DB_PASSWORD=hunter2
============================================================

[!!!] CVE-2026-48827 CONFIRMED — data exfiltrated from outside git root

6c. Test Results

Verified 2026-06-01 using Podman containers (sshd-git 2.17.1 vulnerable, sshd-git 2.18.0 patched).

Testsshd-git 2.17.1 (Vulnerable)sshd-git 2.18.0 (Patched)
Clone repos/allowed.gitSuccess (refs returned)Success (refs returned)
Clone /../secret/secret.gitSuccess — data exfiltratedBLOCKED — no response, exit -1

6d. Patched System Verification

When running against sshd-git 2.18.0 (containerized, same Dockerfile with version override), the traversal path /../secret/secret.git is rejected by the resolveGitRepo() validation. The server closes the channel with no data returned and a non-zero exit status. Legitimate access to repos/allowed.git continues to work normally on the patched server. Both tests were run against identical container environments differing only in the sshd-git dependency version.

7. Detection

Note: The detection rules below are provided as a starting point. Validate and tune them in your own environment before deploying to production.

7a. Network-Based Detection

The attack occurs over the SSH protocol, which is encrypted end-to-end. Therefore, traditional network IDS/IPS cannot inspect the git-upload-pack command payload without SSH decryption (e.g., SSH proxy, TLS termination).

Detection opportunities exist at the application/log level:

Log-Based Detection

Monitor SSHD server logs for git commands containing .. in the repository path:

# Pattern to match in SSHD/application logs
git-upload-pack.*\.\./
git-receive-pack.*\.\./

SSH Session Monitoring

# Sigma rule for SSH git path traversal
title: CVE-2026-48827 Apache MINA SSHD Git Path Traversal Attempt
id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
status: experimental
references:
    - https://lists.apache.org/thread/910kq9ghm6js0k1yhhbrdm9sf5tqq9c9
logsource:
    product: sshd
    service: auth
detection:
    selection:
        CommandLine|contains:
            - 'git-upload-pack'
            - 'git-receive-pack'
    traversal:
        CommandLine|contains:
            - '../'
            - '..\\'
    condition: selection and traversal
level: high

7b. Host-Based Detection (YARA)

Detect Vulnerable sshd-git JAR

rule CVE_2026_48827_Vulnerable_SSHD_Git {
    meta:
        description = "Detects vulnerable Apache MINA SSHD sshd-git JAR (2.0.0 - 2.17.1)"
        cve = "CVE-2026-48827"
        component = "sshd-git"
        severity = "high"
        type = "vulnerability"

    strings:
        $class = "org/apache/sshd/git/pack/GitPackCommand"
        $method = "resolveRootDirectory"
        $fix_absent = "resolveGitRepo"

    condition:
        $class and $method and not $fix_absent
}

Usage:

yara -s CVE_2026_48827.yar /path/to/sshd-git-*.jar

Detect Patched sshd-git JAR

rule CVE_2026_48827_Patched_SSHD_Git {
    meta:
        description = "Detects patched Apache MINA SSHD sshd-git JAR (>= 2.18.0)"
        cve = "CVE-2026-48827"
        component = "sshd-git"
        severity = "informational"
        type = "patch_verification"

    strings:
        $class = "org/apache/sshd/git/pack/GitPackCommand"
        $fix_method = "resolveGitRepo"

    condition:
        $class and $fix_method
}

Detect Exploit Attempt in Logs

rule CVE_2026_48827_Exploit_Attempt {
    meta:
        description = "Detects CVE-2026-48827 path traversal exploit in log files"
        cve = "CVE-2026-48827"
        component = "sshd-git"
        severity = "high"
        type = "exploit_detection"

    strings:
        $cmd1 = "git-upload-pack" ascii
        $cmd2 = "git-receive-pack" ascii
        $traversal1 = "/../" ascii
        $traversal2 = "/../../" ascii
        $traversal3 = "%2e%2e/" ascii nocase

    condition:
        ($cmd1 or $cmd2) and ($traversal1 or $traversal2 or $traversal3)
}
RuleMatch Means
CVE_2026_48827_Vulnerable_SSHD_GitJAR contains vulnerable resolveRootDirectory() without the resolveGitRepo() fix
CVE_2026_48827_Patched_SSHD_GitJAR contains the patched resolveGitRepo() validation method
CVE_2026_48827_Exploit_AttemptLog file contains git commands with directory traversal sequences

8. References

SourceURL
Apache Advisory (Mailing List)https://lists.apache.org/thread/910kq9ghm6js0k1yhhbrdm9sf5tqq9c9
OSS-Securityhttp://www.openwall.com/lists/oss-security/2026/05/30/1
GitHub Advisoryhttps://github.com/advisories/GHSA-mw4m-qhpg-j82m
Vulnerability Lookuphttps://vulnerability.circl.lu/vuln/cve-2026-48827
MITRE CVEhttps://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2026-48827
NVDhttps://nvd.nist.gov/vuln/detail/CVE-2026-48827
Fix Commithttps://github.com/apache/mina-sshd/commit/db0567b33
GitHub Repohttps://github.com/apache/mina-sshd
Apache MINA SSHD Projecthttps://mina.apache.org/sshd-project/