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.
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).
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 — serious impact or readily exploitable. Prioritize remediation.
Affected horde/vfs Versions
horde/vfs · github versions < 3.0.1 are affected.
How to Reproduce CVE-2026-60102
pruva-verify REPRO-2026-00288 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 Proof of Reproduction for CVE-2026-60102
- reached the target end-to-end
- high confidence
- the upstream fix blocks the same trigger
folder/file name supplied to authenticated VFS operations
- Horde_Vfs_Smb::createFolder()
- _command()
- _execute()
- proc_open()
reproduction_steps.sh 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
Root Cause and Exploit Chain for CVE-2026-60102
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_Smbdriver (lib/Horde/Vfs/Smb.php). - Affected versions: All versions before commit
41f74b4acfc144e09013d04dd121e0a5da808361(released asv3.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_folderand captured the resultinguid/gidoutput. - Parity:
fullfor thecreateFolder()path; the same quoting weakness is present in all VFS operations that build thesmbclient -ccommand 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
- Run
bundle/repro/reproduction_steps.shfrom the bundle directory. - The script checks out the vulnerable commit (
41f74b4^) and the fixed commit (41f74b4) ofhorde/Vfsinto a cached repository. - It installs PHP and Composer, then runs
composer installfor each commit. - It creates a fake
smbclientthat logs arguments and exits 0. - It runs a PHP harness that instantiates
Horde_Vfs_Smbwith the fakesmbclientand callscreateFolder('', '$(id > /tmp/horde_vfs_smb_poc_folder).dir'). - Expected evidence: on the vulnerable commit,
/tmp/horde_vfs_smb_poc_folderis created and contains theidoutput; on the fixed commit, the marker is not created and the fakesmbclientreceives the literal payload.
Evidence
bundle/logs/reproduction_steps.log— overall run summary, commit SHAs, and captured fakesmbclientarguments.bundle/logs/vulnerable.log— showsMarker exists: YESwithuid=1000(vscode) ....bundle/logs/fixed.log— showsMarker exists: NO.bundle/logs/vulnerable_smbclient.log— the fakesmbclientreceivesmkdir ".dir";because the shell already executed$(id > ...)and substituted an empty string.bundle/logs/fixed_smbclient.log— the fakesmbclientreceives the literalmkdir "$(id > /tmp/horde_vfs_smb_poc_folder).dir";, confirming no shell expansion occurred.bundle/repro/artifacts/fake_smbclient.sh— the fakesmbclientused 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.1or any release containing commit41f74b4acfc144e09013d04dd121e0a5da808361. - Fix approach: Avoid shell-string execution; pass the
smbclientcommand as anargvarray toproc_open()and quote names inside thesmbclient -cmini-language rather than relying on shell quoting. - Testing recommendations: Add unit tests for
createFolder,write,rename,delete, andreadFilewith 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 fakesmbclientbecause the CVE claims the vulnerability is reachable through authenticated VFS operations. The actual production entry point in a Horde application would call the sameHorde_Vfs_Smbmethods after authentication. - Environment: Ubuntu 26.04, PHP 8.5, Composer 2.9,
horde/Vfsrepository athttps://github.com/horde/Vfs.
Variant Analysis & Alternative Triggers for CVE-2026-60102
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 invokesexecvp()directly. - It explicitly covers every public method that builds a
smbclient -ccommand:readFile,write,writeData,deleteFile,deleteFolder,rename,createFolder,listFolder, and the internal_createRoothelper. - 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-Dargument.Horde_Vfs_Smb::listFolder()— path payload that becomes the-Dargument.
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_Smbdriver (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 originalcreateFolderrepro. On the fixed commit, the impact was blocked. - Parity:
fullagainst 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:
- Passing an argv array to
proc_open(). - Introducing
_quoteSmbArg()to escape the limited set of characters that are special to thesmbclient -cmini-language (\and"). - 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
- Run
bash bundle/vuln_variant/reproduction_steps.shfrom the bundle directory. - The script checks out the vulnerable commit (
41f74b4^) and the fixed commit (41f74b4) ofhorde/Vfs. - It installs PHP and Composer if needed, then runs
composer installfor each commit. - It creates a fake
smbclientthat logs arguments and exits cleanly. - It runs a PHP harness that exercises each variant method with a filename/path containing
$(id > <marker>). - Expected evidence:
- On the vulnerable commit, the marker file is created by shell command substitution before the fake
smbclientruns. - On the fixed commit, the marker file is not created and the fake
smbclientreceives the literal payload.
- On the vulnerable commit, the marker file is created by shell command substitution before the fake
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.logandbundle/logs/vuln_variant/${op}_fixed.log— per-operation output for each commit.bundle/logs/vuln_variant/${op}_vulnerable_smbclient.logandbundle/logs/vuln_variant/${op}_fixed_smbclient.log— fakesmbclientargument 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.phpadded in the fix commit should be kept and extended if any new public method that builds asmbclient -ccommand 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 forssh2_exec(),system(), etc., even though they are outside the scope of this CVE. - Security policy: The repository does not contain a
SECURITY.mdor 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
41f74b4acfc144e09013d04dd121e0a5da808361after 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 sameHorde_Vfs_Smbmethods after authentication. - Environment: Ubuntu 26.04, PHP 8.5, Composer 2.9,
horde/Vfsrepository athttps://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.
Artifacts and Evidence for CVE-2026-60102
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix CVE-2026-60102
Upgrade horde/vfs · github to 3.0.1 or later.
FAQ: CVE-2026-60102
How was CVE-2026-60102 demonstrated?
Which VFS operations can trigger CVE-2026-60102?
Which versions are affected by CVE-2026-60102, and where is it fixed?
How can I reproduce CVE-2026-60102?
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.