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
| Field | Value |
|---|---|
| Primary CWE | CWE-22: Improper Limitation of a Pathname to a Restricted Directory (‘Path Traversal’) |
| Related CWE | CWE-23: Relative Path Traversal |
3. Severity
CVSS 3.1 (from Apache Advisory)
| Field | Value |
|---|---|
| Score | 7.1 (High) |
| Vector | CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:N |
Our Assessment (CVSS 4.0)
| Metric Group | Metric | Value |
|---|---|---|
| Base — Exploitability | Attack Vector (AV) | Network |
| Attack Complexity (AC) | Low | |
| Attack Requirements (AT) | Present | |
| Privileges Required (PR) | Low | |
| User Interaction (UI) | None | |
| Base — Vulnerable System | Confidentiality (VC) | High |
| Integrity (VI) | Low | |
| Availability (VA) | None | |
| Base — Subsequent System | Confidentiality (SC) | None |
| Integrity (SI) | None | |
| Availability (SA) | None | |
| Threat | Exploit Maturity (E) | Proof-of-Concept |
| Field | Value |
|---|---|
| CVSS 4.0 Score | 6.3 (Medium) |
| CVSS 4.0 Vector | CVSS: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-gitmodule — applications using onlysshd-coreorsshd-sftpare 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
| Product | Version Range | Fixed Version | CPE 2.3 |
|---|---|---|---|
| Apache MINA SSHD sshd-git | 2.0.0 - 2.17.1 | 2.18.0 | cpe:2.3:a:apache:sshd:*:*:*:*:*:*:*:* |
| Apache MINA SSHD sshd-git | 3.0.0-M1 - 3.0.0-M3 | 3.0.0-M4 | cpe:2.3:a:apache:sshd:3.0.0:milestone1-3:*:*:*:*:*:* |
Tested Environment (Vulnerable)
| Field | Value |
|---|---|
| Product | Apache MINA SSHD |
| Module | sshd-git |
| Version | 2.17.1 |
| Maven Coordinates | org.apache.sshd:sshd-git:2.17.1 |
| Git Tag | sshd-2.17.1 |
Tested Environment (Patched)
| Field | Value |
|---|---|
| Version | 2.18.0 |
| Maven Coordinates | org.apache.sshd:sshd-git:2.18.0 |
| Git Tag | sshd-2.18.0 |
| Fix Commit | db0567b33 (“Improve git access”) |
5. Root Cause Analysis
5a. Detailed Description
The vulnerability resides in two locations within the sshd-git module:
GitPackCommand.resolveRootDirectory()— handlesgit-upload-packandgit-receive-packSSH commandsEmbeddedCommandRunner.execute()— handles embedded JGit commands via the--git-diroption
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:
| Step | Value | Description |
|---|---|---|
| 1 | Client sends | git-upload-pack '/../secret/secret.git' |
| 2 | Server receives | pathArg = "/../secret/secret.git" |
| 3 | Strip leading / | pathArg = "../secret/secret.git" |
| 4 | rootDir.resolve() | /opt/git-root + ../secret/secret.git = /opt/secret/secret.git |
| 5 | Result | Server opens repository outside the configured root |
Multiple traversal patterns are possible:
| Input Path | After Processing | Escapes Root? |
|---|---|---|
/repos/allowed.git | repos/allowed.git | No (legitimate) |
/../secret/repo.git | ../secret/repo.git | YES |
/repos/../../secret/repo.git | repos/../../secret/repo.git | YES |
/./../../other.git | ./../../other.git | YES |
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:
| Vulnerable | Patched |
|---|---|
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/traversalparentCheck2()— blocks/foo/../../other/traversalsymLinkAllowed()— confirms legitimate symlinks within root still workgitRootAccessDenied()— 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)
| File | Description |
|---|---|
poc_cve_2026_48827.py | Python 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
paramikoinstalled (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).
| Test | sshd-git 2.17.1 (Vulnerable) | sshd-git 2.18.0 (Patched) |
|---|---|---|
Clone repos/allowed.git | Success (refs returned) | Success (refs returned) |
Clone /../secret/secret.git | Success — data exfiltrated | BLOCKED — 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)
}
| Rule | Match Means |
|---|---|
CVE_2026_48827_Vulnerable_SSHD_Git | JAR contains vulnerable resolveRootDirectory() without the resolveGitRepo() fix |
CVE_2026_48827_Patched_SSHD_Git | JAR contains the patched resolveGitRepo() validation method |
CVE_2026_48827_Exploit_Attempt | Log file contains git commands with directory traversal sequences |
8. References
| Source | URL |
|---|---|
| Apache Advisory (Mailing List) | https://lists.apache.org/thread/910kq9ghm6js0k1yhhbrdm9sf5tqq9c9 |
| OSS-Security | http://www.openwall.com/lists/oss-security/2026/05/30/1 |
| GitHub Advisory | https://github.com/advisories/GHSA-mw4m-qhpg-j82m |
| Vulnerability Lookup | https://vulnerability.circl.lu/vuln/cve-2026-48827 |
| MITRE CVE | https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2026-48827 |
| NVD | https://nvd.nist.gov/vuln/detail/CVE-2026-48827 |
| Fix Commit | https://github.com/apache/mina-sshd/commit/db0567b33 |
| GitHub Repo | https://github.com/apache/mina-sshd |
| Apache MINA SSHD Project | https://mina.apache.org/sshd-project/ |