# Variant Root Cause Analysis: CVE-2026-60102

## Summary

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.

## Impact

- **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`.
