All Advisories

mercure

Command Injection via the subservice Log Parameter

On bare-metal (systemd) deployments, mercure's log viewer builds a journalctl shell command and interpolates the subservice query parameter into it without quoting or validation. An administrator can place shell metacharacters in that parameter to run arbitrary commands as the mercure user. Docker deployments take a different code path (a Docker API call, not a shell) and are not affected.

Authored byVolker Schönefeld, Simon Weber2026-05-30
SeverityHighCVSS 7.2CVSS 3.1 VectorAV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:HCWECWE-78 (OS Command Injection)ProductmercureAffected Versionsmercure 0.4.0-beta.1 through 0.4.0-beta.9, systemd (bare-metal) deployments only. Docker deployments are not affected. The parameter was introduced in the 0.4.x branch.Fixed In0.4.1. The subservice parameter is validated against the configured service list, and the shell-injection class was removed across the codebase by routing commands through list-form execution instead of a shell.CVEPendingGHSAPending

Description

mercure is an open-source DICOM orchestration platform; we appreciate the maintainers' prompt, constructive response to this report. The web interface includes a log viewer for mercure's services.

When a service's systemd unit is configured as a list (the "workers" service in the default configuration), the log viewer reads a subservice query parameter and interpolates it into a journalctl command run through a shell:

app/webgui.py:296-313

service_name_or_list = services.services_list[requested_service]["systemd_service"]
if isinstance(service_name_or_list, list):
service_name = request.query_params.get("subservice", "missing")
# ...
sub_services = service_name_or_list
run_result = await async_run(
f"sudo journalctl -n 1000 -u "
f'{service_name} '
f"{start_date_cmd} {end_date_cmd} "
"-o short-iso --no-hostname"
)

View source →

async_run() always runs its argument through a shell, so metacharacters in the parameter are interpreted:

app/webinterface/common.py:67-74

async def async_run(cmd, **params) -> Tuple[Optional[int], bytes, bytes]:
proc = await asyncio.create_subprocess_shell(
cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, **params
)
stdout, stderr = await proc.communicate()
return proc.returncode, stdout, stderr

View source →

A semicolon in the subservice value splits the command; the injected portion runs independently as the mercure user, regardless of whether journalctl succeeds. The endpoint returns 200 and the log view appears empty, so there is no visible indication. We are not publishing a working payload.

This affects systemd installations only. The Docker code path reads the same parameter but passes it to a Docker SDK call rather than a shell, so it is not injectable. On systemd installs the mercure user also holds NOPASSWD sudo for journalctl and systemctl on mercure services, so the injected shell can stop or restart services and read the data and credentials the user owns.

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

Proof-of-concept output

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

Impact

  • On a systemd deployment, an administrator can run arbitrary OS commands as the mercure user by placing shell metacharacters in the log viewer's subservice parameter. Docker deployments are not affected.
  • The mercure user owns the DICOM data directories and configuration, so command execution exposes imaging data in transit and stored target credentials, and the NOPASSWD sudo rights allow stopping or restarting mercure services.

Mitigation

Upgrade to mercure 0.4.1, which validates the subservice value against the configured service list and routes service commands through list-form execution rather than a shell. In general, build external commands as argument vectors (no shell) and allowlist any value that must name a unit or service.

Defender's Checklist

  • Upgrade to 0.4.1 or later

    Deploy the patched release, which validates the subservice value and removes shell execution.

  • Note the systemd scope

    On bare-metal/systemd, prioritize the upgrade; Docker deployments are not affected by this path.

  • Restrict administrator access

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

Severity Reasoning

AV:NThe log viewer is reached over the network through the web interface. This path applies to systemd deployments; Docker deployments use a different code path that is not vulnerable.AC:LA single crafted request triggers the injection.PR:HThe log viewer requires an administrator session.UI:NNo user interaction.S:UExecution stays within the mercure service account's scope.C/I/A:HCommand execution as the mercure user exposes and can modify the data and configuration it owns and can disrupt 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.