Alle Sicherheitshinweise

OpenMRS Core

Stored-Velocity-SSTI zu RCE über ConceptReferenceRange

Die Methode ConceptReferenceRangeUtility.evaluateCriteria() wertet in der Datenbank gespeicherte Kriterien-Strings als Apache-Velocity-Templates ohne Sandbox-Konfiguration aus. Eine Person mit der Berechtigung Manage Concepts kann einen manipulierten Template-Ausdruck hinterlegen, der bei jeder Validierung einer Observation gegen das betroffene Concept ausgeführt wird.

Verfasst vonChiara Fliegner, Volker Schönefeld, Simon WeberErstveröffentlichung 2026-05-04Vollständige Offenlegung 2026-07-24
SchweregradKritischCVSS 9.1CVSS-3.1-VektorAV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:HCWECWE-94 (Improper Control of Generation of Code (Code Injection))ProduktOpenMRS CoreBetroffene Versionen2.7.0 bis 2.7.8; 2.8.0 bis 2.8.5Behoben in2.7.9 und 2.8.6CVECVE-2026-41258GHSAGHSA-xj4f-8jjg-vx4q

Beschreibung

Concept Reference Ranges in OpenMRS führen einen criteria-String mit, damit ein Bereich bedingt gelten kann, etwa nur für Erwachsene oder nur in einem bestimmten klinischen Zustand. Dieser Kriterien-String liegt in der Datenbank und wird als Apache-Velocity-Template ausgewertet. Die auswertende Engine wird pro Aufruf neu erzeugt und ausschließlich mit Logging-Properties initialisiert.

ConceptReferenceRangeUtility.java:71-94 (2.8.4)

VelocityContext velocityContext = new VelocityContext();
velocityContext.put("fn", this);
velocityContext.put("obs", obs);
velocityContext.put("patient", obs.getPerson());
VelocityEngine velocityEngine = new VelocityEngine();
try {
Properties props = new Properties();
props.put("runtime.log.logsystem.class", Log4JLogChute.class.getName());
props.put("runtime.log.logsystem.log4j.category", "velocity");
props.put("runtime.log.logsystem.log4j.logger", "velocity");
velocityEngine.init(props);
}
catch (Exception e) {
throw new APIException("Failed to create the velocity engine: " + e.getMessage(), e);
}
StringWriter writer = new StringWriter();
String wrappedCriteria = "#set( $criteria = " + criteria + " )$criteria";
try {
velocityEngine.evaluate(velocityContext, writer, ConceptReferenceRangeUtility.class.getName(), wrappedCriteria);
return Boolean.parseBoolean(writer.toString());
}

View source →

Die Property-Liste enthält keinen Eintrag introspector.uberspect.class, Velocity fällt also auf seine Standardimplementierung UberspectImpl zurück. Diese filtert die Introspection nicht: Die Sperrlisten des SecureUberspector, die Template-Ausdrücke von java.lang.Class, java.lang.Runtime und java.lang.ProcessBuilder fernhalten, greifen nicht. Jedes in den Kontext gelegte Objekt ist damit für beliebige Methodenaufrufe erreichbar, getClass() eingeschlossen, und von einem Class-Handle aus erreicht ein Template-Ausdruck über Reflection den Rest der JVM. Der Kriterien-String wird unverändert in das Template einkonkateniert, der gespeicherte Wert ist also der Ausdruck.

Reflection ist der Weg zur Codeausführung, für den Abfluss von Patientendaten ist sie aber nicht nötig. Bereits die Kontextobjekte legen den Datensatz unmittelbar offen:

Ausdruck im KontextErreichte DatenKategorie
$patient.getGivenName(), $patient.getFamilyName()Vollständiger NamePII
$patient.getBirthdate(), $patient.getAge()Geburtsdatum, AlterPII
$patient.getPatientIdentifier()Patientennummer, nationale KennungPII
$obs.getValueNumeric()Der erfasste klinische WertPHI
$fn.getLatestObs(...)Jede frühere Observation der Person, einschließlich Diagnosen, HIV-Status und MedikationPHI

Ausgelöst wird die Auswertung nicht von der Person, die die Kriterien hinterlegt hat. Sie läuft bei der Validierung einer Observation, also bei derjenigen Person oder Integration, die als Nächste eine Observation gegen das betroffene Concept erfasst.

ObsValidator.java:302-306 (2.8.4)

ConceptReferenceRangeUtility referenceRangeUtility = new ConceptReferenceRangeUtility();
List<ConceptReferenceRange> validRanges = new ArrayList<>();
for (ConceptReferenceRange referenceRange : referenceRanges) {
if (referenceRangeUtility.evaluateCriteria(StringEscapeUtils.unescapeHtml4(referenceRange.getCriteria()), obs)) {

View source →

Der gespeicherte String läuft auf dem Weg zur Auswertung durch StringEscapeUtils.unescapeHtml4(), eine beim Speichern angewandte HTML-Entity-Kodierung wird also wieder rückgängig gemacht. Jede Eingabefilterung, die sich auf die escapte Form verlässt, übersteht diesen Umlauf nicht. In 2.8.5 ist die Aufrufstelle von ObsValidator nach ConceptServiceImpl gewandert; die Sink-Stelle und die fehlende Introspection-Policy sind unverändert.

Beschrieben wird das Kriterien-Feld über das Concept-Formular der Legacy-UI unter /dictionary/concept.form, die übliche Oberfläche für die Pflege des Concept-Dictionary und auf praktisch jedem Deployment vorhanden. ConceptFormValidator prüft die numerischen Grenzen einer Reference Range und sieht sich den Inhalt der Kriterien überhaupt nicht an, der String erreicht die Datenbank also so, wie er getippt wurde. Erforderlich ist die Berechtigung Manage Concepts, die der OpenMRS Implementers' Guide den Rollen Data Assistant, Data Manager und Informatics Manager zuweist. Es ist eine Content-Management-Berechtigung von Datenerfassungs- und Aufsichtspersonal, keine administrative: kein Superuser, kein System Developer.

Wir haben die vollständige Kette auf OpenMRS Core 2.8.4 mit webservices.rest 2.50.0 und legacyui 2.0.0 bestätigt. Der Test legt eine Rolle mit Manage Concepts und den zugehörigen Leseberechtigungen an, erzeugt eine Person in dieser Rolle, hinterlegt als diese Person über das Concept-Formular einen Kriterien-Payload und erfasst anschließend als anderer Nutzer über die REST-API eine Observation gegen das Concept. Die Ausführung wird durch eine ausgehende Anfrage des Serverprozesses an einen lokalen Listener bestätigt.

Proof-of-Concept-Ausgabe (Kennungen gekürzt)

[*] Phase 1: Creating low-privilege user (as admin)
[+] Role created: PoC Clerk
Privileges: Get Concept Datatypes, Manage Concepts, ...
[+] User created: clerk<id>
[*] Phase 2: Planting criteria as 'clerk<id>'
[+] Authenticated as 'clerk<id>'
[+] Concept created: <uuid>
[*] Phase 3: Recording an observation
LISTEN 0.0.0.0:9999
POST /openmrs/ws/rest/v1/obs
Code execution confirmed via HTTP callback
FROM: concept dictionary editor (data clerk)
TO: arbitrary code execution (Tomcat process)

Der Payload verbleibt in der Tabelle concept_reference_range und läuft bei jeder weiteren Observation gegen dieses Concept erneut, für jede Person und jeden API-Client, über Neustarts hinweg. Im Datensatz der Observation weist nichts darauf hin, dass eine Auswertung stattgefunden hat.

OpenMRS hat nicht den von uns vorgeschlagenen SecureUberspector ergänzt. Velocity wurde aus diesem Pfad vollständig entfernt und durch eine Spring-SpEL-Auswertung ersetzt, die ein SimpleEvaluationContext beschränkt.

ConceptReferenceRangeUtility.java:74-76 (2.8.6)

private static final SimpleEvaluationContext EVAL_CONTEXT = SimpleEvaluationContext
.forPropertyAccessors(new MapAccessor(), DataBindingPropertyAccessor.forReadOnlyAccess())
.withMethodResolvers(DataBindingMethodResolver.forInstanceMethodInvocation()).build();

View source →

SimpleEvaluationContext ist Springs bewusst reduzierter Kontext: keine Bean-Referenzen, keine Typ-Referenzen und damit kein Weg von einem Ausdruck zu Class oder zu beliebigen Konstruktoren. Der Property-Zugriff ist read-only, und Methodenaufrufe sind auf Instanzmethoden der tatsächlich gebundenen Objekte beschränkt. Die zuvor als $fn bereitgestellten Hilfsfunktionen wurden zudem aus der Utility-Klasse in eine eigene Klasse CriteriaFunctions verschoben, mit einem Kommentar zur Begründung: So ist evaluateCriteria selbst aus einem Ausdruck heraus nicht mehr aufrufbar. Die Änderung ging als Commit 0801023b am 08.04.2026 ein und erschien am 10.04.2026 in 2.8.6, drei Wochen vor Veröffentlichung des Advisories.

Auswirkung

  • Persistentes Ausführen von beliebigem Programmcode als Benutzer des Application-Server-Prozesses, automatisch ausgelöst bei jeder weiteren Validierung einer Observation gegen das betroffene Concept.
  • Rechteausweitung von Manage Concepts (eine Content-Management-Berechtigung, die typischerweise von Datenerfassungspersonal gehalten wird) hin zu beliebiger Codeausführung.
  • Offenlegung von Patientendaten (PHI) über den Template-Kontext, einschließlich Patienten-IDs, demografischer Daten und klinischer Observations.

Abhilfe

Aktualisieren Sie OpenMRS Core auf 2.7.9 oder 2.8.6. Bis zur Aktualisierung beschränken Sie die Berechtigung Manage Concepts auf vertrauenswürdige Personen und prüfen Sie bestehende Einträge in ConceptReferenceRange-Kriterien in der Datenbank.

Checkliste für Betreiber

  • Auf 2.7.9 oder 2.8.6 aktualisieren.

    Betroffen sind 2.7.0 bis 2.7.8 und 2.8.0 bis 2.8.5. Der Fix ersetzt die Velocity-Auswertung durch einen beschränkten SpEL-Kontext; auf einer ungepatchten Version gibt es keinen Konfigurationsschalter mit derselben Wirkung.

  • Gespeicherte Kriterien vor und nach dem Update prüfen.

    Fragen Sie concept_reference_range nach Kriterien-Strings ab und sehen Sie sich alles an, was kein einfacher Vergleich ist. Ein auf verwundbarer Version hinterlegter Payload überlebt das Update; die neue Auswertung weist ihn ab statt ihn auszuführen, aber der Datensatz steht weiterhin dort und zeigt, ob es einen Versuch gab.

  • Prüfen, wer Manage Concepts tatsächlich hält.

    Sehen Sie sich die Rollenzuordnung an, statt die Berechtigung für administrativ zu halten. Sie hängt häufig an Datenerfassungsrollen, weil die Pflege des Concept-Dictionary klinischer Alltag ist.

  • Auf ausgehende Verbindungen des Anwendungsprozesses achten.

    Codeausführung an dieser Stelle läuft im Servlet-Container. Ausgehender Verkehr vom OpenMRS-Host zu etwas anderem als den bekannten Integrationen ist unabhängig von diesem Befund eine Alarmierung wert.

  • Weitere Template-Auswertungen in eigenen Modulen prüfen.

    Jede VelocityEngine, die ohne introspector.uberspect.class auf SecureUberspector initialisiert wird, hat dieselbe Exposition, sobald der Template-Text aus gespeicherten Daten stammt. OpenMRS hatte das bereits im Modul htmlformentry (CVE-2020-24621).

Bewertung im Detail

AV:NBeide Hälften der Kette laufen über die HTTP-Schnittstellen der Anwendung selbst: das Concept-Formular zum Hinterlegen der Kriterien, die REST-API zum Erfassen der auslösenden Observation.AC:LKeine Race Condition, keine zeitliche Abhängigkeit, keine deployment-spezifische Voraussetzung. Die Engine wird bei jedem Aufruf gleich initialisiert.PR:HZum Beschreiben des Kriterien-Felds ist eine bestimmte vergebene Berechtigung nötig. Siehe die Anmerkung unten dazu, was diese Berechtigung praktisch bedeutet.UI:NDie Auswertung wird durch gewöhnliche klinische Datenerfassung gegen das betroffene Concept ausgelöst, nicht dadurch, dass jemand zu einer Handlung verleitet wird.S:CDie Ausführung verlässt das Berechtigungsmodell der Anwendung vollständig und landet im Prozess des Servlet-Containers, einer anderen Sicherheitsautorität als die OpenMRS-Rolle, die den String hinterlegt hat.C:HDer Auswertungskontext legt Patienten-IDs, demografische Daten und Observations unmittelbar offen, und die Codeausführung erreicht alles, was der Prozess lesen kann.I:HCodeausführung als Anwendungsprozess erlaubt es, klinische Datensätze über dieselbe Service-Schicht zu verändern, die auch die Anwendung nutzt.A:HDieselbe Ausführung kann den Dienst anhalten oder beeinträchtigen.

PR:H bildet ab, dass eine benannte Berechtigung nötig ist, überzeichnet als CVSS-Label aber, wie privilegiert deren Inhaber ist. Manage Concepts ist eine Content-Management-Berechtigung, die der OpenMRS Implementers' Guide Datenerfassungs- und Aufsichtspersonal zuweist, nicht Administratoren. Der Abstand von dieser Rolle bis zur Codeausführung als Serverprozess ist der eigentliche Befund.

Referenzen

So können wir helfen

Wer wir sind

Die Sicherheitsforscher hinter diesem Sicherheitshinweis.

Dr. Simon Weber Profile

Dr. rer. nat. Simon Weber

Senior Pentester & MedSec-Forscher

Ich evaluiere Ihr SaMD mit derselben branchenprägenden Sicherheitsexpertise, die ich dem BAK MV für die Überarbeitung des B3S-Standards beigetragen habe.

  • Promotion über Krankenhaus-Cybersicherheit
  • Kritische Schwachstellen in Krankenhaussystemen gefunden
  • Alumni der THB MedSec-Forschungsgruppe
  • gematik Security Hero
Volker Schönefeld Profile

Dipl.-Inf. Volker Schönefeld

Senior Application Security Expert

Als ehemaliger CTO und Entwickler, der zum Pentester wurde, arbeite ich mit Ihrem Team zusammen, um Schwachstellen aufzudecken und Lösungen zu finden, die zu Ihrer Architektur passen.

  • 20+ Jahre als CTO, 50+ Mio. App-Downloads
  • Architektur und Absicherung großer IoT-Flotten
  • Certified Web Exploitation Specialist
  • gematik Security Hero

Penetrationstest gesucht?

Machine Spirits ist spezialisiert auf Sicherheitsbewertungen für Medizinprodukte und Gesundheits-IT. Von MDR-Penetrationstests bis C5-Cloud-Compliance helfen wir MedTech-Unternehmen, regulatorische Anforderungen zu erfüllen.