Skip to content

Seek and Destroy 1.38.5

Choose a tag to compare

@connortechnology connortechnology released this 05 Oct 22:39

RPM packages: the 1.38.5 RPM builds failed on Rocky Linux 8, so no 1.38.5 RPMs were published. Use 1.38.6, which is 1.38.5 with that build fix.

ZoneMinder 1.38.5 Release Notes

This is a maintenance release for the 1.38 series. It is primarily a security
release: it fixes 19 reported vulnerabilities, including an authenticated remote
code execution, a heap overflow and a stack overflow, as well as cross-monitor
authorization bypasses throughout the web UI and API. All 1.38 users should upgrade.

⚠️ Upgrade notes

  • Run zmupdate.pl -f after upgrading (the packages do this for you). It now
    replaces a ZM_AUTH_HASH_SECRET that is empty or still the shipped default with a
    random secret. Token (JWT) authentication is refused while the secret is the
    public default, so on a source install that skips this step API logins answer 403
    until a secret is set in Options → System.
  • The 1.38.5 database update adds an index on Sessions.access and moves Controls
    rows from the removed onvif protocol module to ONVIF.
  • The API no longer answers actions a controller does not implement with generic
    handlers. Such requests, including Controls add/edit and Configs add, now return
    404.
  • Adding a monitor to a group, removing it, re-parenting the group or deleting it
    now requires edit permission on each monitor affected, in both the web UI and
    the API.
  • Forcing, cancelling or disabling a monitor's alarms now requires edit permission
    on that monitor.
  • ZM_GO2RTC_PATH may now be given as the go2rtc server address (http://host:1984)
    or its API path (http://host:1984/api); before, only the /api form worked.

Key Highlights

🔒 Security & Hardening

Code execution and memory safety

  • Request data could invoke arbitrary object methods such as save() and
    execute(); through the filter debug modal this was authenticated OS command
    execution. Only field accessors can now be set from request data (GHSA-vvx7-ghpv-jq98)
  • Image::ReadJpeg() decoded a larger JPEG into the monitor-sized buffer, a heap
    overflow reachable by whoever can write a File monitor's source; File monitors
    now also reject a mismatched size (GHSA-rpp4-xmqm-84ff)
  • RTSP handshake field parsing is bounded (GHSA-xp4c-29hh-2w4j)
  • Event video generation runs the encoder without a shell and quotes every
    argument (GHSA-pfph-4j9j-7cv7, GHSA-pxq8-5c8j-xf3r)
  • Filter AutoExecute tag values are kept away from shell parsing, and filter email
    frames are extracted without a shell
  • Stored filter terms are validated and quoted in zmfilter (GHSA-p8h3-4x5c-cv7p)
  • The monitor id is validated before it reaches the Janus PIN shell command
  • Event DefaultVideo and Name are treated as filenames everywhere, so they cannot
    escape the event directory

Authentication

  • The shipped default ZM_AUTH_HASH_SECRET is replaced with a random secret on
    upgrade and refused for token signing and verification (GHSA-wmcc-x64g-jr84)
  • /api/host/login issues the token to the account that authenticated
    (GHSA-m77q-66v7-j3fq)
  • X-Forwarded-For is trusted for auth hash IP binding only from configured
    proxies (GHSA-72rf-54rm-798c)
  • Every AJAX request requires authentication regardless of view (GHSA-vvw3-j4p4-4rgx)
  • A non-string request parameter no longer satisfies authentication
  • Password migration in zmupdate.pl gave every user the same bcrypt salt; each now
    gets a random one, and legacy hashes are rehashed on every password login

Access control

  • Event delete, archive and edits are authorized on the event's own monitor
    (GHSA-34x2-mw89-c52f)
  • Frames and EventData API adds require edit on the target event (GHSA-993c-fc6p-hpxg)
  • Zone API writes and reads require permission on the zone's monitor
    (GHSA-f8h6-62c9-x6qr)
  • zms event streams are authorized on the event's own monitor, not a monitor id the
    client supplies (GHSA-4r2m-68p2-phf7)
  • The Config API requires System permission and withholds secrets (GHSA-m896-3fc6-2jf5)
  • API edits can no longer redirect a save to another record by an Id in the request body
  • The per-monitor ACL is applied throughout the Events API (search, console counts,
    add, edit), and a user denied every monitor now sees none rather than all of them
  • Snapshots and Tags no longer expose or attach events from denied monitors
  • Group membership changes require edit on each monitor they move
  • Log deletion, ZonePresets, the monitor daemon actions, control definitions and host
    load endpoints check permissions; user preferences answer to their owner

Injection, XSS and request hardening

  • Monitor fields written into page scripts are escaped (GHSA-r44j-mvj8-cg9w)
  • The nested filter id is normalised and escaped (GHSA-wqmm-rmvc-pc7r)
  • filter[query] is escaped in export column links (GHSA-626g-6988-pv4p)
  • Names and paths are escaped wherever they are written into HTML and JavaScript
  • The image proxy no longer follows redirects past its SSRF guard, connects to the
    address it checked, requires a CSRF token and refuses cross-site requests
    (GHSA-v2qc-p8cq-g4pc)
  • view_video validates HTTP Range requests instead of trusting the client's offsets
  • Client-supplied web log fields are stripped of control characters
  • Sessions are stored only for clients that carry a session cookie, and the session
    garbage collector uses an index and a two-phase delete
  • The Perl scripts' taint-safe PATH no longer adds group-writable directories and is
    configurable

🐛 Correctness & Recording

  • zms can stream the event a monitor is currently recording
  • A zms that ends on its ttl or frame count exits instead of waiting forever
  • MonitorStream playback state is shared safely with the command thread (#4939)
  • Image::Fill, Outline, Delta and Overlay walk rows by linesize, fixing
    striped alarm highlights on widths whose rows are not 32-byte aligned
  • The blob labeller no longer runs out of tags and abandons the frame
  • Hard links fall back to rename on filesystems without them, and EPERM on FreeBSD is
    not mistaken for missing support (#5048)
  • OnDemand capture keeps its last image available while paused
  • Deadlocked queries are retried with bounded backoff
  • Storage DiskSpace is adjusted atomically
  • A lock stops a second zmcontrol server per monitor (#4423)
  • zmfilter's start delay applies to the daemon it was meant for, and zmupdate and
    zmfilter reconnect to the database instead of pinging it
  • ONVIF: VideoSource token discovery, 401 handling on PullMessages, subscription
    renewal for short lifetimes, and no busy-polling of cameras that don't hold
    PullMessages
  • The lowercase ZoneMinder::Control::onvif module, which collided with ONVIF.pm on
    case-insensitive filesystems, is removed (#5122)
  • Config type mismatches are non-fatal
  • zmupdate records each migration's own version as it goes, so a failed upgrade resumes
    where it stopped, and it no longer applies migrations newer than the installed version
  • go2rtc and Janus no longer point at ZoneMinder's RTSP restream when ZM_MIN_RTSP_PORT
    is not set, which gave them an unplayable port-0 URL; they use the camera stream

🖥️ Web UI

  • Prev/next event no longer loops between two events that start in the same second
  • The events table reports a failed query instead of loading forever (#3301)
  • Montage review keeps tracking events that are still recording and aborts every
    in-flight query; monitor names are no longer shown as HTML entities
  • The cycle view no longer leaks a timer on every restore (#5135)
  • The Control view sends PTZ commands to the monitor's own server
  • Event playback rate buttons stay within the rate list; ZM_WEB_SHOW_PROGRESS is honoured
  • The timeline view no longer fails for users who cannot view every monitor
  • Monitor deletion reports a failure instead of ignoring it (#4215)
  • CSV monitor import works again (#4962); export downloads get a proper filename
  • The Events entry is back in the classic navbar
  • A monitor left on the Auto player no longer pops up "ZM_GO2RTC_PATH is empty" when
    go2rtc is not configured; the next player is used instead

🔌 API

  • States can be viewed, edited and deleted by Id
  • Monitors are soft-deleted like the console does
  • An unknown filter field answers 400 naming it instead of 500
  • The DateTime filter term works
  • The CakePHP cache prefix includes the ZoneMinder version, so an upgrade does not
    reuse a stale cache

📦 Platform & Packaging

  • OpenBSD rc.d script and Apache configuration (#5152)
  • ZM_SERVER_NAME resolves to its server id
  • The gnutls libcurl runtime library is listed in the Debian Depends
  • The zmrepo source is not added when a PPA already provides ZoneMinder (#4945)
  • Ubuntu 26.04 builds

Credits

Thanks to everyone who reported security issues through GitHub's private vulnerability
reporting: Return-Zer0, Haind03, DavidKorczynski (Ada Logics; found by Anthropic using
Claude), 4n86rakam1, qianyuzz, alex131125, SounLabs, phuongmai1212, jasonbernier and
alham-rizvi. The corresponding advisories are published alongside this release.

Full Changelog: 1.38.4...1.38.5