Any enabled, API-enabled account could read the effective configuration,
including secrets, whatever its permissions. Confirmed on a live instance: a
user with every area set to None retrieved ZM_DB_PASS from
/api/configs/viewByName/ZM_DB_PASS.json.
The controller already required System Edit to change a config but checked
nothing at all to read one, so index, view, viewByName and categories were open
to anyone the API let in. They now require the same System permission the web
ui's Options page requires.
Permission alone is not enough, because nothing outside the server has any use
for these values. Two kinds are now withheld from every caller, administrators
included:
- Rows flagged Private in the Config table. That flag already existed and was
loaded into $zm_config, but nothing ever acted on it. It covers
ZM_AUTH_HASH_SECRET, which is enough to forge an authentication hash for any
user, and the reCaptcha secret.
- The database credentials. These are read from zm.conf and conf.d rather than
the table, so they have no row to flag and are listed by name. This is also
why filtering the table alone would not have been enough: index appends the
file-backed values to its response.
Requesting an unknown name logged print_r($zm_config, true), copying the whole
effective configuration into the log and anything collecting it. It now logs the
name at Debug.
Verified against a live instance, before and after, with a temporary System=None
API user: reading ZM_DB_PASS returned the password and now returns 401; an
administrator gets an empty Value for ZM_DB_PASS and ZM_AUTH_HASH_SECRET, the
293-entry index carries no secret values, and ordinary settings such as
ZM_WEB_TITLE still read normally.
The config-api RCE fix (b036408a5) guards edit() and delete() with
($user['System'] == 'Edit'), but $user is a ZM\User object that does
not implement ArrayAccess and whose System property is protected. Array
access on it raises "Error: Cannot use object of type ZM\User as array",
so the endpoint fatals with HTTP 500 for every authenticated user. This
blocks the RCE only by accident and also breaks config editing for
legitimate System=Edit admins.
Use $user->System(), matching the idiom in every other API controller
(States, Servers, Monitors, Users, etc.).
Refs GHSA-mvj8-mqqq-2w5f.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add RBAC checks to ConfigsController edit() and delete() requiring
System=Edit permission, matching the pattern used by other controllers.
Harden System/Readonly column checks with !empty() to handle missing
columns gracefully. Fix command injection in Event.php by using
ZM_PATH_FFMPEG constant with escapeshellarg() instead of hardcoded
unsanitized ffmpeg call. Add is_executable() validation at all exec()
sites using ZM_PATH_FFMPEG as defense-in-depth against poisoned config
values.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>