Skip to content

CVE-2026-60102: Verified Repro With Script Download

CVE-2026-60102: Horde VFS < 3.0.1 OS command injection via Horde Vfs Smb driver

CVE-2026-60102 is verified against horde/vfs · github. Affected versions: < 3.0.1. Fixed in 3.0.1. Vulnerability class: Command Injection. This high reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00288.

REPRO-2026-00288 horde/vfs · github Command Injection Variant found Jul 14, 2026 CVE entry ↗ .txt
Severity
HIGH
CVSS
8.8
Confidence
HIGH
Reproduced in
12m 50s
Tool calls
135
Spend
$1.30
01 · Overview

What Is CVE-2026-60102?

CVE-2026-60102 is a high-severity OS command injection in the Horde VFS API (Horde_Vfs_Smb driver) before 3.0.1. Authenticated users can inject shell commands through crafted filenames used in normal VFS operations. Pruva reproduced it (reproduction REPRO-2026-00288).

02 · Severity & CVSS

CVE-2026-60102 Severity & CVSS Score

CVE-2026-60102 is rated high severity, with a CVSS base score of 8.8 out of 10.

HIGH threat level
8.8 / 10 CVSS base
03 · Affected Versions

Affected horde/vfs Versions

horde/vfs · github versions < 3.0.1 are affected.

How to Reproduce CVE-2026-60102

$ pruva-verify REPRO-2026-00288
or curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00288/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh
Run in a VM or disposable container. This exploits a real vulnerability.
06 · Proof of Reproduction

Proof of Reproduction for CVE-2026-60102

Remote code execution — reproduced
  • reached the target end-to-end
  • high confidence
  • the upstream fix blocks the same trigger
Trigger

folder/file name supplied to authenticated VFS operations

Attack chain
  1. Horde_Vfs_Smb::createFolder()
  2. _command()
  3. _execute()
  4. proc_open()
Runnable proof: reproduction_steps.sh
Captured evidence: vulnerable smbclientfixed smbclient
Variants tested

Alternate Horde_Vfs_Smb public methods (deleteFile, writeData, rename, readFile, deleteFolder, isFolder, listFolder) reach the same shell-injection sink as the original createFolder repro. The v3.0.1 fix covers all of them, so no bypass exists.

How the agent worked 320 events · 135 tool calls · 13 min
13 minDuration
135Tool calls
89Reasoning steps
320Events
1Dead-ends
Agent activity over 13 min
Support
30
Hypothesis
2
Repro
123
Judge
27
Variant
134
0:0012:50

Root Cause and Exploit Chain for CVE-2026-60102

Versions: All versions before commit 41f74b4acfc144e09013d04dd121e0a5da808361 (released as v3.0.1).

Horde VFS before 3.0.1 builds a shell command string in Horde_Vfs_Smb::_command() and executes it with proc_open(). The _escapeShellCommand() helper only escapes semicolons and backslashes, leaving command substitution ($(...)) and backticks intact. A crafted file or folder name containing $(...) is therefore interpolated into a double-quoted shell context and executed by /bin/sh -c before the configured smbclient binary is ever invoked. We confirmed this by calling the real Horde_Vfs_Smb::createFolder() method with a folder name of $(id > /tmp/horde_vfs_smb_poc_folder).dir; the vulnerable commit creates the marker file with id output, while the fixed commit leaves the marker untouched and passes the literal payload as an argument to smbclient.

  • Package/component: Horde VFS, specifically the Horde_Vfs_Smb driver (lib/Horde/Vfs/Smb.php).
  • Affected versions: All versions before commit 41f74b4acfc144e09013d04dd121e0a5da808361 (released as v3.0.1).
  • Risk level: High. An authenticated caller can inject arbitrary shell commands through normal VFS operations such as file upload, create folder, rename, delete, or read paths.
  • Consequences: Full OS command execution as the user running the PHP process.

Impact Parity

  • Disclosed/claimed maximum impact: Code execution (OS command injection) via proc_open() / /bin/sh -c.
  • Reproduced impact from this run: We demonstrated arbitrary command execution by causing the shell to run id > /tmp/horde_vfs_smb_poc_folder and captured the resulting uid/gid output.
  • Parity: full for the createFolder() path; the same quoting weakness is present in all VFS operations that build the smbclient -c command string.
  • Not demonstrated: No remote authentication bypass was needed; the reproduction assumes an already authenticated VFS caller as described in the CVE.

Root Cause

In the vulnerable code (lib/Horde/Vfs/Smb.php before 41f74b4^), _command() concatenates user-supplied paths/names into a single string:

$fullcmd = $this->_params['smbclient']
    . ' "//' . $this->_params['hostspec'] . '/' . $share . '"'
    . ' "-U' . $this->_params['username'] . '"'
    . ' -D "' . $path . '"'
    . ' -c "';
foreach ($cmd as $c) {
    $fullcmd .= $c . ";";
}
$fullcmd .= '"';
return $this->_execute($fullcmd);

_execute() passes the string directly to proc_open(), which invokes the configured shell (/bin/sh -c) when the command is not an array. The _escapeShellCommand() helper only escapes ; and \, so $, backticks, parentheses, and quotes remain active. A folder name like $(id > /tmp/horde_vfs_smb_poc_folder).dir is first quoted in a double-quoted mkdir "..." segment, but the shell still expands $(...) before running smbclient.

The fix (41f74b4acfc144e09013d04dd121e0a5da808361) replaces the shell string with an argv array passed to proc_open() and adds _quoteSmbArg() to escape single and double quotes inside the smbclient -c mini-language. Because proc_open() with an array calls execvp() directly, no shell is involved and shell metacharacters lose their special meaning.

Reproduction Steps

  1. Run bundle/repro/reproduction_steps.sh from the bundle directory.
  2. The script checks out the vulnerable commit (41f74b4^) and the fixed commit (41f74b4) of horde/Vfs into a cached repository.
  3. It installs PHP and Composer, then runs composer install for each commit.
  4. It creates a fake smbclient that logs arguments and exits 0.
  5. It runs a PHP harness that instantiates Horde_Vfs_Smb with the fake smbclient and calls createFolder('', '$(id > /tmp/horde_vfs_smb_poc_folder).dir').
  6. Expected evidence: on the vulnerable commit, /tmp/horde_vfs_smb_poc_folder is created and contains the id output; on the fixed commit, the marker is not created and the fake smbclient receives the literal payload.

Evidence

  • bundle/logs/reproduction_steps.log — overall run summary, commit SHAs, and captured fake smbclient arguments.
  • bundle/logs/vulnerable.log — shows Marker exists: YES with uid=1000(vscode) ....
  • bundle/logs/fixed.log — shows Marker exists: NO.
  • bundle/logs/vulnerable_smbclient.log — the fake smbclient receives mkdir ".dir"; because the shell already executed $(id > ...) and substituted an empty string.
  • bundle/logs/fixed_smbclient.log — the fake smbclient receives the literal mkdir "$(id > /tmp/horde_vfs_smb_poc_folder).dir";, confirming no shell expansion occurred.
  • bundle/repro/artifacts/fake_smbclient.sh — the fake smbclient used as the command target.
  • bundle/repro/artifacts/harness.php — the PHP harness that drives the real library path.

Key excerpts:

=== vulnerable (994f4a46fd76ea44eae2fe839216bb273ce49080) ===
Marker exists: YES
Marker content: uid=1000(vscode) gid=1000(vscode) groups=1000(vscode),969(969)
=== fixed (41f74b4acfc144e09013d04dd121e0a5da808361) ===
Marker exists: NO
ARG //127.0.0.1/share
ARG -c
ARG mkdir "$(id > /tmp/horde_vfs_smb_poc_folder).dir";

Recommendations / Next Steps

  • Upgrade guidance: Upgrade to Horde VFS v3.0.1 or any release containing commit 41f74b4acfc144e09013d04dd121e0a5da808361.
  • Fix approach: Avoid shell-string execution; pass the smbclient command as an argv array to proc_open() and quote names inside the smbclient -c mini-language rather than relying on shell quoting.
  • Testing recommendations: Add unit tests for createFolder, write, rename, delete, and readFile with payloads containing $(...), backticks, pipes, semicolons, and quotes to prevent regression.

Additional Notes

  • Idempotency: The reproduction script was run twice consecutively with identical results; both the vulnerable and fixed commits behaved consistently.
  • Limitations: The proof uses the library API (function_call) with a fake smbclient because the CVE claims the vulnerability is reachable through authenticated VFS operations. The actual production entry point in a Horde application would call the same Horde_Vfs_Smb methods after authentication.
  • Environment: Ubuntu 26.04, PHP 8.5, Composer 2.9, horde/Vfs repository at https://github.com/horde/Vfs.

Variant Analysis & Alternative Triggers for CVE-2026-60102

Versions: All versions before commit 41f74b4acfc144e09013d04dd121e0a5da808361 (v3.0.0 and earlier).

The original CVE-2026-60102 reproduction exercised Horde_Vfs_Smb::createFolder() with a filename containing $(id > /tmp/...). The variant stage searched for additional entry points that reach the same shell-command sink (_command() -> _execute() -> proc_open() with a string) and tested them against the patched commit 41f74b4acfc144e09013d04dd121e0a5da808361 (Horde VFS v3.0.1). Eight alternate paths were confirmed to fire on the vulnerable commit, but none of them bypass the v3.0.1 fix: deleteFile, writeData, rename, readFile, deleteFolder, isFolder, and listFolder (path-controlled) are all covered by the switch to an argv-based proc_open() and the _quoteSmbArg() helper.

Fix Coverage / Assumptions

  • The v3.0.1 fix relies on the invariant that proc_open() called with an array bypasses the shell entirely and invokes execvp() directly.
  • It explicitly covers every public method that builds a smbclient -c command: readFile, write, writeData, deleteFile, deleteFolder, rename, createFolder, listFolder, and the internal _createRoot helper.
  • It also covers config-derived values (hostspec, username, port, ipaddress, domain, share) by passing them as separate argv elements.
  • The fix does not leave any remaining shell-string proc_open() call in the SMB driver.

Variant / Alternate Trigger

The tested variants are alternate public-method entry points to the same vulnerable sink in lib/Horde/Vfs/Smb.php:

  • Horde_Vfs_Smb::createFolder() — filename payload (original repro path).
  • Horde_Vfs_Smb::deleteFile() — filename payload.
  • Horde_Vfs_Smb::writeData() / write() — destination filename payload.
  • Horde_Vfs_Smb::rename() — new filename payload.
  • Horde_Vfs_Smb::readFile() — source filename payload.
  • Horde_Vfs_Smb::deleteFolder() — folder-name payload.
  • Horde_Vfs_Smb::isFolder() — name payload that becomes part of the -D argument.
  • Horde_Vfs_Smb::listFolder() — path payload that becomes the -D argument.

In the vulnerable code, all of these reach Horde_Vfs_Smb::_command() and Horde_Vfs_Smb::_execute(), which pass a single shell command string to proc_open(). The old _escapeShellCommand() only escaped ; and \, so $(...) and backticks in the filename/path were expanded by /bin/sh -c before smbclient ran.

  • Package/component: Horde VFS, Horde_Vfs_Smb driver (lib/Horde/Vfs/Smb.php).
  • Affected versions: All versions before commit 41f74b4acfc144e09013d04dd121e0a5da808361 (v3.0.0 and earlier).
  • Risk level: High on vulnerable versions; authenticated callers can execute arbitrary OS commands as the PHP process user.
  • Consequences: Full OS command execution.

Impact Parity

  • Disclosed/claimed maximum impact: OS command execution via proc_open() / /bin/sh -c.
  • Reproduced impact from this variant run: On the vulnerable commit, every tested alternate method caused the shell to execute id > <marker>, producing the same code-execution impact as the original createFolder repro. On the fixed commit, the impact was blocked.
  • Parity: full against the vulnerable version; the fix prevents the same payloads from working on v3.0.1.
  • Not demonstrated: No remote authentication bypass; the reproduction assumes an authenticated VFS caller, consistent with the CVE.

Root Cause

The root cause is the same as the original finding: Horde_Vfs_Smb::_command() constructs a single shell command string and Horde_Vfs_Smb::_execute() passes it to proc_open(). The old _escapeShellCommand() helper is insufficient because it does not neutralize shell command substitution ($(...)), backticks, quotes, or command separators. Any public method that interpolates a user-controlled filename or path into the command string is therefore an OS command-injection vector.

The fix (commit 41f74b4acfc144e09013d04dd121e0a5da808361) removes the shell string entirely by:

  1. Passing an argv array to proc_open().
  2. Introducing _quoteSmbArg() to escape the limited set of characters that are special to the smbclient -c mini-language (\ and ").
  3. Updating every caller to use _quoteSmbArg() for filenames and path components.

Because no shell is involved, the alternate entry points are neutralized without requiring per-method patches.

Reproduction Steps

  1. Run bash bundle/vuln_variant/reproduction_steps.sh from the bundle directory.
  2. The script checks out the vulnerable commit (41f74b4^) and the fixed commit (41f74b4) of horde/Vfs.
  3. It installs PHP and Composer if needed, then runs composer install for each commit.
  4. It creates a fake smbclient that logs arguments and exits cleanly.
  5. It runs a PHP harness that exercises each variant method with a filename/path containing $(id > <marker>).
  6. Expected evidence:
    • On the vulnerable commit, the marker file is created by shell command substitution before the fake smbclient runs.
    • On the fixed commit, the marker file is not created and the fake smbclient receives the literal payload.

Evidence

  • bundle/logs/vuln_variant/variant_run.log — overall summary of all variant attempts, including which fired on the vulnerable commit and which were blocked on the fixed commit.
  • bundle/logs/vuln_variant/${op}_vulnerable.log and bundle/logs/vuln_variant/${op}_fixed.log — per-operation output for each commit.
  • bundle/logs/vuln_variant/${op}_vulnerable_smbclient.log and bundle/logs/vuln_variant/${op}_fixed_smbclient.log — fake smbclient argument captures.
  • bundle/logs/vuln_variant/marker_${op} — marker files created during vulnerable runs (their presence is the proof of command execution).

Key excerpts from variant_run.log:

Variants that fired on vulnerable commit: 8/8
Variants that fired on fixed commit:      0/8
NO BYPASS: the fixed commit covers all tested variant paths.

Fixed-commit example (createFolder) showing the literal payload reaches the fake smbclient and no marker is created:

ARG //127.0.0.1/share
ARG -U
ARG user
ARG -D
ARG /vfs/
ARG -c
ARG mkdir "safe$(id > .../marker_createFolder).txt";

Recommendations / Next Steps

  • No code change is required to close the reported CVE; the v3.0.1 fix already covers the alternate entry points we identified.
  • Regression coverage: The upstream test test/Unit/SmbCommandInjectionTest.php added in the fix commit should be kept and extended if any new public method that builds a smbclient -c command is introduced in the future.
  • Future hardening: Avoid proc_open() with string commands anywhere in the VFS drivers; use argv arrays and language-specific quoting helpers. Audit other drivers for ssh2_exec(), system(), etc., even though they are outside the scope of this CVE.
  • Security policy: The repository does not contain a SECURITY.md or explicit threat model. Adding one would clarify which caller-supplied inputs are considered trusted and which must be treated as attacker-controlled.

Additional Notes

  • Idempotency: The script was run twice consecutively with identical results; the final repository state remained at the fixed commit.
  • Repository state: The project cache repo was restored to 41f74b4acfc144e09013d04dd121e0a5da808361 after the second run.
  • Limitations: The proof uses the library API with a fake smbclient. The actual production entry point in a Horde application would call the same Horde_Vfs_Smb methods after authentication.
  • Environment: Ubuntu 26.04, PHP 8.5, Composer 2.9, horde/Vfs repository at https://github.com/horde/Vfs.

CVE-2026-60102 Reproduction Transcript

The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.

Full session Replay every step — scrub the timeline or play it back.

Event 1/40
0:004:19
0:00
session startedaccounts/fireworks/models/kimi-k2p7-code · CVE-2026-60102 · REPRO-20
0:03
0:05
web search
0:08
0:09
0:13
0:14
0:23
0:24
0:30
0:30
0:35
0:38
0:45
1:04
1:15
1:16
web search
3:34
3:34
extract_facts
no facts extracted
3:36
3:36
3:36
supportrepro
4:16
4:18
4:18
4:18
4:19

Artifacts and Evidence for CVE-2026-60102

Scripts, logs, diffs, and output captured during the reproduction.

bundle/artifact_promotion_manifest.json13.2 KB
bundle/artifact_promotion_report.json13.2 KB
bundle/vuln_variant/source_identity.json0.7 KB
bundle/vuln_variant/root_cause_equivalence.json2.2 KB
bundle/repro/reproduction_steps.sh6.7 KB
bundle/logs/reproduction_steps.log0.8 KB
bundle/logs/vulnerable.log0.3 KB
bundle/logs/fixed.log0.2 KB
bundle/repro/rca_report.md6.2 KB
bundle/repro/validation_verdict.json0.7 KB
bundle/repro/runtime_manifest.json0.8 KB
bundle/logs/vulnerable_smbclient.log0.1 KB
bundle/logs/fixed_smbclient.log0.2 KB
bundle/repro/artifacts/fake_smbclient.sh0.2 KB
bundle/repro/artifacts/harness.php1.1 KB
bundle/vuln_variant/reproduction_steps.sh6.4 KB
bundle/logs/vuln_variant/variant_run.log5.9 KB
bundle/vuln_variant/validation_verdict.json1.0 KB
bundle/vuln_variant/variant_manifest.json2.7 KB
bundle/vuln_variant/patch_analysis.md5.9 KB
bundle/vuln_variant/rca_report.md7.6 KB
bundle/vuln_variant/runtime_manifest.json1.6 KB
bundle/logs/vuln_variant/createFolder_vulnerable.log0.3 KB
bundle/logs/vuln_variant/createFolder_fixed.log0.3 KB
bundle/logs/vuln_variant/deleteFile_vulnerable.log0.3 KB
bundle/logs/vuln_variant/deleteFile_fixed.log0.3 KB
bundle/logs/vuln_variant/writeData_vulnerable.log0.3 KB
bundle/logs/vuln_variant/writeData_fixed.log0.3 KB
bundle/logs/vuln_variant/rename_vulnerable.log0.3 KB
bundle/logs/vuln_variant/rename_fixed.log0.3 KB
bundle/logs/vuln_variant/readFile_vulnerable.log0.3 KB
bundle/logs/vuln_variant/readFile_fixed.log0.3 KB
bundle/logs/vuln_variant/deleteFolder_vulnerable.log0.3 KB
bundle/logs/vuln_variant/deleteFolder_fixed.log0.3 KB
bundle/logs/vuln_variant/isFolder_vulnerable.log0.3 KB
bundle/logs/vuln_variant/isFolder_fixed.log0.3 KB
bundle/logs/vuln_variant/listFolderPath_vulnerable.log0.3 KB
bundle/logs/vuln_variant/listFolderPath_fixed.log0.3 KB
bundle/vuln_variant/artifacts/fake_smbclient.sh0.2 KB
bundle/vuln_variant/artifacts/harness.php1.6 KB
08 · How to Fix

How to Fix CVE-2026-60102

Upgrade horde/vfs · github to 3.0.1 or later.

Coming soon

Step-by-step mitigation and hardening guidance for CVE-2026-60102 — configuration checks, workarounds where no patch exists, and how to verify you're protected — is on the way.

10 · FAQ

FAQ: CVE-2026-60102

How was CVE-2026-60102 demonstrated?

The reproduction calls the real Horde_Vfs_Smb::createFolder() with a folder name of '$(id > /tmp/horde_vfs_smb_poc_folder).dir'. On the vulnerable commit the marker file is created with id output; on the fixed commit the literal payload is passed as an argument to smbclient and no command runs.

Which VFS operations can trigger CVE-2026-60102?

Any operation that passes an attacker-controlled name into the SMB driver — upload, create folder, rename, delete, or read paths — because the name reaches the unsafely-built shell command in Horde_Vfs_Smb::_command().

Which versions are affected by CVE-2026-60102, and where is it fixed?

Horde VFS versions < 3.0.1 are affected. It is fixed in 3.0.1, which eliminates shell-string execution and hardens SMB argument quoting — upgrade to 3.0.1 or later.

How can I reproduce CVE-2026-60102?

Download the verified script from this page and run it in an isolated environment against Horde VFS < 3.0.1. It invokes createFolder() with a $(...) payload and shows the injected command executing, then confirms 3.0.1 treats the payload as a literal argument.
11 · References

References for CVE-2026-60102

Authoritative sources for CVE-2026-60102 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.