CVE-2026-39931 Overview
CVE-2026-39931 is an authenticated SQL injection vulnerability in OpenEMR through version 8.2.0. The flaw resides in the backup configuration import feature accessible through backup.php when invoked with the form_step=202 parameter. Administrators holding admin or super Access Control List (ACL) privileges can upload a crafted SQL file that OpenEMR passes to the MySQL command-line client through an unfiltered shell_exec call. The resulting arbitrary Data Definition Language (DDL) and Data Manipulation Language (DML) execution grants attackers full control over the application database and, in permissive configurations, the underlying filesystem.
Critical Impact
Attackers with admin ACL access can extract credential hashes, inject backdoor accounts, plant persistent triggers, and write arbitrary files where MySQL FILE privileges and secure_file_priv allow.
Affected Products
- OpenEMR versions up to and including 8.2.0
- Deployments exposing the backup import feature at backup.php?form_step=202
- Installations where MySQL FILE privileges and permissive secure_file_priv settings are configured
Discovery Timeline
- 2026-08-03 - CVE-2026-39931 published to the National Vulnerability Database (NVD)
- 2026-08-03 - Last updated in NVD database
Technical Details for CVE-2026-39931
Vulnerability Analysis
OpenEMR exposes a backup restoration workflow through backup.php. When an authenticated administrator submits a request with form_step=202, the application accepts an uploaded SQL file and forwards it to the mysql command-line client through a PHP shell_exec invocation. OpenEMR does not parse, filter, or restrict the contents of the uploaded file before passing it to the database engine.
The uploaded SQL is executed with the privileges of the OpenEMR database user, which typically holds broad rights across the application schema. Any DDL or DML statement contained in the file runs directly against the database. This includes reading credential hashes from user tables, updating ACL tables to elevate accounts, creating triggers or stored procedures for persistence, and issuing SELECT ... INTO OUTFILE statements to drop files onto disk.
The vulnerability is categorized as [CWE-434] Unrestricted Upload of File with Dangerous Type, reflecting the missing content validation on the imported SQL payload.
Root Cause
The root cause is the absence of input validation between the file upload handler and the shell_exec call that spawns mysql. OpenEMR treats any file uploaded to the form_step=202 code path as trusted backup content, when the ACL check on the calling user is the only gate. There is no SQL parser, statement allowlist, or scope restriction applied to the imported script.
Attack Vector
An attacker must first obtain an authenticated session as an admin or super ACL user, whether through credential compromise, social engineering, or misuse by an insider. The attacker then submits a POST request to backup.php with form_step=202 and an attached SQL file containing arbitrary statements. When MySQL is configured with FILE privileges and a permissive secure_file_priv value, the payload can escape the database boundary and write PHP webshells or other executables into the web root, enabling remote code execution.
Refer to the Jiva Security CVE-2026-39931 Analysis and the VulnCheck OpenEMR SQL Injection Advisory for full technical details.
Detection Methods for CVE-2026-39931
Indicators of Compromise
- POST requests to backup.php containing the form_step=202 parameter from user sessions that do not routinely perform backup operations
- New rows in OpenEMR users_secure, users, or ACL tables not corresponding to any legitimate provisioning event
- Unexpected triggers, stored procedures, or events registered in the OpenEMR database schema
- Files written to the web root or MySQL data directory matching the timestamp of a form_step=202 request
Detection Strategies
- Alert on any invocation of mysql as a child process of the PHP-FPM or Apache worker serving OpenEMR
- Parse OpenEMR access logs for the pattern backup.php combined with form_step=202 and correlate with the authenticated user identity
- Enable MySQL general query logging on OpenEMR database instances and flag DDL statements originating from application sessions
Monitoring Recommendations
- Track privilege changes to admin and super ACL groups in OpenEMR and require out-of-band confirmation for new members
- Baseline the expected frequency of backup restore operations and alert on deviations
- Monitor filesystem writes to the OpenEMR web root and MySQL data directory outside of package updates
How to Mitigate CVE-2026-39931
Immediate Actions Required
- Restrict network access to backup.php to trusted administrative networks only, using web server rules or a reverse proxy allowlist
- Review the membership of the admin and super ACL groups and remove accounts that do not require backup privileges
- Rotate credentials for all OpenEMR administrator accounts and audit recent logins for anomalous source addresses
- Audit the OpenEMR database for unexpected triggers, stored procedures, events, and ACL rows introduced since the last known-good backup
Patch Information
At the time of publication, refer to the VulnCheck OpenEMR SQL Injection Advisory and upstream OpenEMR release channels for patched builds superseding 8.2.0. Apply vendor-supplied fixes as soon as they are validated in a staging environment.
Workarounds
- Remove or rename backup.php on production installations where administrative backup import is not required
- Configure MySQL with secure_file_priv set to a restricted directory and revoke the FILE privilege from the OpenEMR database account to block filesystem writes via SQL
- Run OpenEMR behind a Web Application Firewall (WAF) rule that blocks multipart uploads targeting backup.php with form_step=202 from unauthorized source ranges
- Enforce multi-factor authentication for all admin and super ACL accounts to raise the cost of session compromise
# Configuration example: restrict MySQL file operations for the OpenEMR account
mysql -u root -p -e "REVOKE FILE ON *.* FROM 'openemr'@'localhost'; FLUSH PRIVILEGES;"
# Restrict secure_file_priv in my.cnf
# [mysqld]
# secure_file_priv=/var/lib/mysql-files/
# Apache rule to block external access to backup.php
# <Files "backup.php">
# Require ip 10.0.0.0/8
# </Files>
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

