Skip to content

Non-admin uid/gid injection in create_event/update_event → run-as-arbitrary-user (incl. root) — sibling of GHSA-5j3v-cq96-xw6v

High
jhuckaby published GHSA-9xpg-9w3g-mpwj Jun 19, 2026

Package

cronicle (GitHub)

Affected versions

<= 0.9.120

Patched versions

0.9.121

Description

Summary

A non-admin user (or a scoped API key) holding only create_events/run_events can add
an extra top-level uid (and gid) field to an event. The event-data validator passes
unknown keys through, the value is stored verbatim, and at job launch the (root) daemon
setuids the spawned child to that uid. Setting the run-as-user is an admin-only control
everywhere else (the Plugin "Run as User" Advanced option is admin/requireAdmin gated).
Result: a low-privilege user runs jobs as ANY user, including root, on the typical
root-daemon install.

Affected: v0.9.120 (current), HEAD f5a5388. Severity: privilege escalation; on the default
root daemon, low-priv → root code execution on the Cronicle host.

Root cause (file:line)

  • lib/api.js:160-232 requireValidEventData → validateOptionalParams is a PARTIAL allowlist
    that only checks keys present in its rules object and silently passes unknown keys.
    uid/gid are NOT in the rules, so they are stored verbatim on the event.
  • lib/job.js:192 launchJob copies the whole event into the job.
  • lib/job.js:220 if (plugin.uid) job.uid = plugin.uid; — the plugin uid overrides the
    event uid ONLY when the plugin defines one. The shipped default plugins (Shell Script
    shellplug, HTTP Request urlplug) have no uid, so the attacker's event-level uid survives.
  • lib/job.js:462 child_opts.uid = job.uid || process.getuid();
  • lib/job.js:490-499 getpwnam(child_opts.uid) → child_opts.uid/gid set → cp.spawn setuid by
    the root daemon.

This is a distinct sibling of the patched CVE-2026-39401 / GHSA-5j3v-cq96-xw6v, which covered
uid/gid injected via JOB OUTPUT and is gated at lib/job.js:716-718. The API-side event
uid/gid injection is not covered by that fix and, to our knowledge, is unpublished.

Reproduction (live, via the API)

Daemon runs as root (default per docs/Setup.md). As admin, create a NON-ADMIN API key with
only create/run-event privileges. Then, using that key:

POST /api/app/create_event
{"api_key":"","title":"x","enabled":0,"category":"general","plugin":"shellplug",
"target":"","timing":{},"params":{"script":"#!/bin/sh\nid\n"}, "uid":0}
POST /api/app/run_event {"api_key":"","id":"<event_id>"}

Observed (script id output): the job runs as uid=0(root). Injecting "uid":65534
instead runs it as uid=65534(nobody) — proving the non-admin's injected uid is honored
(it differs from the default daemon uid). get_event shows the uid field stored from the
non-admin key (validator passthrough). Full transcript in LIVE_VERIFICATION.md; a scripted
repro is in exploit.sh.

Tested only on a local container with a harmless id payload; no third-party system accessed.

Remediation

In launchJob, unconditionally delete event-level uid/gid unless they come from the plugin
(mirror the lib/job.js:220 override logic with an explicit strip). Alternatively, add uid/gid
to the validator allowlist guarded by an admin/privilege check, so non-admins cannot set them.

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
Low
User interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
High
Availability
High

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

CVE ID

CVE-2026-58255

Weaknesses

Improper Privilege Management

The product does not properly assign, modify, track, or check privileges for an actor, creating an unintended sphere of control for that actor. Learn more on MITRE.

Missing Authorization

The product does not perform an authorization check when an actor attempts to access a resource or perform an action. Learn more on MITRE.

Improperly Controlled Modification of Dynamically-Determined Object Attributes

The product receives input from an upstream component that specifies multiple attributes, properties, or fields that are to be initialized or updated in an object, but it does not properly control which attributes can be modified. Learn more on MITRE.

Credits