ZM_Object's constructor treats an array as the object's row, and validInt()
and validCardinal() returned an array as they got it (preg_replace() does).
So wherever a request id reached `new ZM\X(...)`, raw or through those
validators, a crafted array built an object whose Id, UserId or MonitorId
the sender chose. The permission check then looked at those, and the delete
or update that followed used the Id. A user with Events=View deleted another
user's filter by posting Id[Id]=<filter>&Id[UserId]=<self> to the filter
delete action; the same shape reached events (MonitorId), users, reports,
snapshots and storage.
validInt() and validCardinal() now give '' for anything that isn't a single
value. The places that built an object from a raw $_REQUEST value (the
control and event ajax handlers, the event, filter, report, snapshot and
storage actions, view_hls, and the download and event detail modals) take
the id through validCardinal().
tests/php/test_request_ids_scalar.php covers the validators with arrays and
fails if web code builds an object straight from $_REQUEST, $_GET or $_POST.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Follow-up to review comments on PR #5022.
addtag passed $_REQUEST['tid'] and $_REQUEST['id'] straight into the
Events_Tags INSERT and the Tags UPDATE, while only the TagOrder call
sanitized tid. A non-numeric tid therefore still inserted a row with the
value coerced to 0, and only the recency bookkeeping was skipped.
Validate both ids once at the top of the case, reject non-scalar values
(array-form parameters such as tid[]=1, which validCardinal would return
as an array and bind as one), and reuse the sanitized values for all
three statements.
TagOrder.php calls dbFetchOne/dbQuery but did not pull in the database
helpers itself, relying on the caller having done so. Both current call
sites happen to, but that is incidental; require_once('database.php') to
match Tag.php and User_Preference.php.
- When making an AJAX request, we pass an array of file paths relative to DIR_EXPORTS_DOWNLOAD , not absolute ones.
- Check $fileName for a non-empty string.
- Clear fileExistencePollingInterval if it exists before calling again.
- More correctly retrieve parameters from the "el.href" string using "new URL(el.href)".
- Disabled the cursor for "a.disabled".
Earlier commits in this branch dropped reflected user input from
ajaxError() messages entirely to close Snyk XSS findings, but that
lost useful diagnostic information ("Unrecognised action <name>",
"Insufficient permissions for user <user>"). Use validHtmlStr()
(htmlspecialchars with ENT_QUOTES) so the values still appear in
the message and Snyk recognises the sanitization. Affects
add_monitors.php, device.php, event.php, events.php, log.php.
The download action passed $_REQUEST['exportFormat'] and connkey
through downloadEvents() into ajaxResponse() unsanitized. Constrain
exportFormat to the allowlist ('tar'/'zip') and strip non-[\w.-]
chars from connkey at the call site.
$_REQUEST['exportFile'] and $_REQUEST['connkey'] flowed through
exportEvents() into ajaxResponse() (i.e. exit(jsonEncode())).
exportEvents() already sanitizes both internally via the same
preg_replace, but Snyk can't trace through the function — sanitize
at the call site so the taint is closed at the boundary.
The catch-all ajaxError() at the bottom of event.php reflected both
$_REQUEST['action'] and the session username. Same Snyk pattern as
add_monitors.php and device.php. Action is now logged server-side
via ZM\\Warning; user-facing message is static.
This change will allow the use of any national Unicode alphabet in the file name.
We also allow any numeric character and the characters "-", ".", "(", ")"
The ":" character is prohibited in file names in Windows, so we're excluding it from the list of allowed characters.
fix(tag): Create tags on mobile
chore(tags): Change TagName to Name
chore(tags): eslint
chore(tags): dbFetchAll to dbQuery for removetag
chore(events): eslint (attempt 2)
feat(tags): Better handling of keyboard
fix(tags): Enter key for creating new tag
fix(tags): Don't allow space as a tag name
feat(tags): Delete tag if last assignment removed
fix(tags): Increase height of dropdown
in progress
fix(Tags): Use T.Id on the events page dropdown
fix(Tags): Remove $availableTags from events.php
chore(sql): Formatting sql statements
feat(Tags): Working OR on filters and events pages
fix(filter): Populate availableTags
chore(Tags): code formatting
fix(tag): Add tag on create tag
Fix(tags): Remove tag from available if last
feat(tags): Add zm_update.sql
fix(chosen): Undo css width
fix(chosen): tags dropdown width
fix(tags): dropdown over timeline
fix(tags): Full width input
fix(events): Refresh table on page show
chore(filter): Clean up availableTags
chore(event): Clean up available & selected Tags
fix(event): Update available tags on remove
fix(event): Remove hack for selected tags
feat(tags): Blur input after adding tag
doc(tags): Initial tags documentation
fix(tags): Dark theme dropdown
fix(tags): Dark theme for tags on input
fix(tags): Dark theme for highlight in dropdown
fix(tags): Populate filter tags droplist
chore(): Bump zm_update to 1.37.42
chore(tags): Move mobile check to skin.js
chore(tags): Comment debug statements
fix(tags): Enter key to create tag on mobile Chome
chore(tags): Space in 'All Tags' for translation
Temporary commit to handle cookie expiration times
chore(tags): Remove unnecessary Tag(s) from en_gb
chore(): Cleanup unnecessary Error and Debug
chore(): Resolve merge conflicts
chore(): Address merge conflicts with master