Select Page

SQL Injection in OpenEMR Identified and Recommendations

This post documents a blind time-based SQL injection in the PostCalendar module discovered
in OpenEMR 8.0.0. The SQL Injection is exploitable by an authenticated admin user and illustrates how a single determined attacker with a valid session can move from nuisance to catastrophe.

OpenEMR is one of the most widely deployed open-source EHR platforms worldwide, used by clinics, hospitals, and solo practitioners around the world. Vulnerabilities in sensitive software like OpenEMR go beyond just unauthorized access. A compromised session is a compromised patient chart. A SQL injection is a path to every record in the database. At the time of writing, there are 1,150 OpenEMR instances accessible on the internet, with 864 hosted in the United States. Healthcare organizations remain frequent targets of cyberattacks, making widely deployed systems like OpenEMR particularly attractive to threat actors.

OpenEMR is a full-featured EHR and practice management system.

It handles:

  • Patient demographics and insurance data
  • Clinical encounter notes, diagnoses, and SOAP records
  • Prescription management and e-prescribing
  • Lab orders, results, and imaging links
  • Billing, scheduling, and appointment management
  • Role-based access control across clinical staff

OpenEMR’s open-source nature makes it the default choice for resource-constrained providers,
particularly rural clinics, and community health centers in low-income regions. Many deployments run on-premises with limited IT staff and infrequent patching cycles. This is not a criticism; it is a compliment to the devoted developers who built this important system for resource-constrained entities. However, this reality shapes how seriously vulnerabilities in this codebase need to be treated.

Blind Time-Based SQL Injection in PostCalendar

The PostCalendar module, a legacy calendar component embedded in OpenEMR, contains an
unparameterized SQL injection in its category deletion function. The dels POST parameter is
interpolated directly into a DELETE SQL statement with no escaping, then executed via Doctrine
DBAL’s executeStatement().

What is Blind SQL Injection

Blind SQL Injection is a type of cyberattack that targets a website’s database through its input
fields, like login forms, search boxes, or URLs. It happens when a website fails to properly check
or sanitize the information users submit before sending it to the database. In a normal SQL
Injection, attackers can directly see database errors or data returned by the system. In Blind
SQL Injection: the system does not show database results or errors, so attackers can’t see the
data directly.

Instead, attackers ask the database true or false questions and watch how the website
responds. For example, they might ask a question like: “Is the first letter of the admin password
‘A’?” If the website behaves differently when the statement is true versus false, such as loading
a different page, responding more slowly, or returning a different message, the attacker learns a
small piece of information.

By repeating these questions thousands of times, attackers can slowly reconstruct sensitive
data such as usernames, passwords, or other confidential database information. In simple
terms, Blind SQL Injection is like guessing the answers to a locked database by observing how
the system reacts, even though the system never directly shows the data.

The Root Cause

The code path spans three files:
1. pnadmin.php (line 376) – injection point

$dels = pnVarCleanFromInput('dels'); // HTML-only sanitizer. SQL metacharacters untouched $delete = "DELETE FROM $pntable[postcalendar_categories] WHERE pc_catid IN ($dels)"; pnModAPIFunc(__POSTCALENDAR__, 'admin', 'deleteCategories', ['delete' => $delete]);

2. pnAPI.php (lines 40 – 58) – register_globals simulation
The PostCalendar module simulates PHP’s deprecated register_globals by extracting all superglobals into the PHP global scope. This means every HTTP parameter, GET, POST,
cookie, session is accessible as a named PHP variable:

foreach (['_POST', '_GET', '_COOKIE', '_SESSION', ...] as $__s) { extract(${$__s}, EXTR_OVERWRITE); }

3. pnadminapi.php (line 57) – execution sink

$conn->executeStatement($delete); // Raw SQL string - no binding, no escaping

pnVarCleanFromInput() was designed to prevent HTML Injection. It strips<script>,<iframe>, <object>, and similar tags. Single quotes, double dashes, and parentheses pass
through untouched and are the exact characters needed for SQL Injection. The modern
OpenEMR codebase universally uses sqlQuery(“… WHERE id = ?”, [$id]) with bound
parameters. PostCalendar predates this convention and was never updated.

Proof-of-Concept Exploitation

Confirm Injection via Timing
Clearwater’s penetration testing team developed a Proof-of-Concept (PoC) in Python to
automate the SQL Injection process and extract sensitive information from the database.

python3 openemr_sqli_poc.py --host 192.168.50.109 --port 8080 \ -u admin -p pass --dump database --verbose

The Bigger Picture: Why Vulnerabilities in EHR Systems Are Different

Most web application vulnerabilities are discussed in terms of their technical severity. CVSS
scores, exploitability ratings, and attack vectors. In healthcare software, those numbers sit alongside the human cost of a breach. Credit card numbers can be canceled. Passwords can be
changed. PHI cannot be unexposed. A patient’s HIV status, psychiatric diagnosis, history of
addiction, or surgical history, once leaked, is leaked forever. These are not data points; they are
facts about a person’s body that they shared under conditions of clinical trust.

Remediation

Clearwater disclosed the SQL Injection vulnerability to the team at OpenEMR with
recommendations on how to fix the vulnerable code. As of 03/24/2026, OpenEMR 8.0.1 has
patched the vulnerable code to protect the application from the disclosed SQL Injection. The full security advisory can be found here: https://github.com/openemr/openemr/security/advisories/GHSA-rq3v-38×5-3rm5


// Before (vulnerable):
$dels = pnVarCleanFromInput('dels');
$delete = "DELETE FROM $pntable[postcalendar_categories] WHERE pc_catid IN
($dels)";

// After (safe):
$dels_raw = pnVarCleanFromInput('dels');
$ids = array_filter(array_map('intval', explode(',', $dels_raw)));
if (!empty($ids)) {
$placeholders = implode(',', array_fill(0, count($ids), '?'));
sqlStatement(
"DELETE FROM " . escape_table_name('postcalendar_categories') . "
WHERE pc_catid IN ($placeholders)",
$ids
);
}

Additional Recommendations

  • Implement a robust patch management program. Regularly monitor for vendor updates and
    security advisories and apply patches promptly to reduce exposure to known vulnerabilities.
  • Limit internet exposure of sensitive applications. Systems such as OpenEMR should not be
    directly accessible from the public internet unless absolutely necessary. Where possible,
    place them behind VPNs, reverse proxies, or other access controls.
  • Apply the Principle of Least Privilege. Ensure users and system accounts only have access
    to the features, endpoints, and data required for their role. This helps minimize the impact if
    an account or application component is compromised.
  • Deploy a Web Application Firewall (WAF). A properly configured WAF can help detect and
    block common web attacks, including SQL Injection attempts, providing an additional
    defensive layer.
  • Conduct regular security testing. Routine vulnerability assessments and penetration testing
    can help identify security weaknesses before attackers discover and exploit them.

 

This vulnerability was discovered by our Clearwater Technical Testing Consultants:

SME Highlight

Porter Throckmorton, CBBH

Senior Consultant, Technical Testing Services

Read More

SME Highlight

Matthew Sheimo, MS, CISSP, CRISC, OSCP, CARTP

Senior Principal Consultant, Technical Testing Services

Read More

Register Today to Get Monthly Invites

Related Blogs

No results found.