All Advisories

mercure

Code Injection via eval() Sandbox Escape in Routing Rules

mercure's routing engine decides where incoming DICOM series go by evaluating administrator-defined rule expressions with Python's eval(), sandboxed only by an empty set of builtins. That sandbox is escaped with a standard object-traversal technique, so an administrator (or anyone who reaches an admin session) can run arbitrary code. A saved rule runs on every DICOM series the router evaluates.

Authored byVolker Schönefeld, Simon Weber2026-05-30
SeverityCriticalCVSS 9.1CVSS 3.1 VectorAV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:HCWECWE-94 (Improper Control of Generation of Code (Code Injection))ProductmercureAffected VersionsAll releases from 0.2.0-beta.1 through 0.4.0-beta.9.Fixed In0.4.1. Rule evaluation now uses asteval with a cleared symbol table by default; the previous eval() path runs only when the MERCURE_UNSAFE_RULE_EVALUATION environment variable is set.CVEPendingGHSAPending

Description

mercure is an open-source DICOM orchestration platform; we appreciate the maintainers' prompt, constructive response to this report. The routing engine decides where each incoming DICOM series goes by evaluating administrator-defined rule expressions.

Those expressions are evaluated with Python's eval(), sandboxed only by an empty builtins mapping:

app/common/rule_evaluation.py:49-71

safe_eval_cmds = {"float": float, "int": int, "str": str, "len": len, "bool": bool,
"sum": sum, "round": round, "max": max, "min": min, "abs": abs,
"pow": pow, "chr": chr, "ord": ord}
def eval_rule(rule: str, tags_dict: Dict[str, str]) -> Any:
# ...
rule = replace_tags(rule, tags_dict)
# ...
tags_obj = Tags(tags_dict)
result = eval(rule, {"__builtins__": {}}, {**safe_eval_cmds, "tags": tags_obj})
# ...
return result, tags_obj.tags_accessed()

View source →

An empty __builtins__ removes direct access to built-in functions, but it does not stop object traversal: from any literal one can reach the base object class, enumerate its subclasses, and reach a class that exposes the real import machinery, regaining __import__ and arbitrary command execution. The vendor's own 0.4.1 release notes describe this as "trivially bypassed via MRO class traversal." We are not publishing a working payload.

The rule field reaches the evaluator without validation. HTML sanitization is applied to the tags field but not to the rule field:

app/webinterface/rules.py:269

testrule = form["rule"] # raw form input, no validation
testvalues = json.loads(form["testvalues"])
result, attrs_accessed = rule_evaluation.eval_rule(testrule, testvalues)

View source →

Two paths reach the sink. /rules/test evaluates the expression immediately and returns the result in the HTTP response, so command output and file contents come straight back to the caller. A rule saved via /rules/edit/{rule} persists in mercure.json and runs on every DICOM series the router evaluates, giving persistent execution.

Evaluation runs inside the web interface process (immediate path) or the router process (saved path), so execution reaches the shared filesystem, Redis, and the rest of the Docker network. The scope-change metric reflects that.

Command execution as the mercure user is confirmed by the returned output of an injected id:

Proof-of-concept output

uid=1000(mercure) gid=1000(mercure) groups=1000(mercure)

Impact

  • An attacker with an administrator session can execute arbitrary code on the mercure host. The /rules/test endpoint returns the result inline, so files, environment variables, and credentials can be read directly through the web interface.
  • A routing rule saved by the attacker runs on every DICOM series the router evaluates, providing persistent code execution that fires automatically as imaging data flows through.
  • Because evaluation runs inside the web interface or router process, the attacker reaches the shared filesystem, Redis, and other containers on the Docker network.

Mitigation

Upgrade to mercure 0.4.1, which evaluates rules with asteval and a cleared symbol table by default; the previous eval() path runs only when MERCURE_UNSAFE_RULE_EVALUATION is set, which should not be enabled. For any code in this shape, avoid eval() on user-supplied expressions: use a purpose-built safe evaluator (such as asteval) or a small parsed comparison DSL, and reject attribute access to dunder or private names.

Defender's Checklist

  • Upgrade to 0.4.1 or later

    Deploy the patched release and confirm MERCURE_UNSAFE_RULE_EVALUATION is not set in any environment.

  • Review saved routing rules

    Audit mercure.json for rule expressions that contain attribute access or function calls beyond simple tag comparisons; these should not be present.

  • Restrict administrator access

    Limit who holds administrator credentials and confirm the default admin password has been changed.

Severity Reasoning

AV:NBoth the immediate (/rules/test) and persistent (saved rule) paths are reached over the network through the web interface.AC:LA single request carrying a crafted expression is sufficient.PR:HThe rule endpoints require an administrator session.UI:NThe immediate path returns the result directly; saved rules fire on automatic series evaluation without user interaction.S:CEvaluation in the web interface or router process reaches resources beyond that component: the shared filesystem, Redis, and other containers.C/I/A:HArbitrary code execution yields full read, modification, and denial over reachable data and services.

References

How We Can Help

Who We Are

The security researchers behind this advisory.

Dr. Simon Weber Profile

Dr. rer. nat. Simon Weber

Senior Pentester & MedSec Researcher

I evaluate your SaMD with the same industry-defining security insight I contributed to the BAK MV for the revision of the B3S standard.

  • PhD on Hospital Cybersecurity
  • Critical vulnerabilities found in hospital systems
  • Alumni of THB MedSec Research Group
  • gematik Security Hero
Volker Schönefeld Profile

Dipl.-Inf. Volker Schönefeld

Senior Application Security Expert

As a former CTO and developer turned pentester, I work alongside your team to uncover vulnerabilities and find solutions that fit your architecture.

  • 20+ years as CTO, 50M+ app downloads
  • Architected and secured large-scale IoT fleets
  • Certified Web Exploitation Specialist
  • gematik Security Hero

Looking for a Penetration Test?

Machine Spirits specializes in security assessments for medical devices and healthcare IT. From MDR penetration testing to C5 cloud compliance, we help MedTech companies meet regulatory requirements.