# Root Cause Analysis: CVE-2026-60102

## Summary

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

## Impact

- **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:

```php
$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`.
