Skip to content

Zimbra coordinated wave: /h/* attribute override, Classic XSS chain, Zimlet proxy allowlist, null-identity iCalendar, sudo-argument LPEs

Source: hourly offensive-security scan, 2026-10-23. Zimbra published a seventeen-GHSA coordinated wave at 15:31Z against Zimbra Collaboration Suite (ZCS) 10.1.x. These are unusually detailed vendor-written advisories — endpoint names, parameter names, filter classes, and partial-fix state are all disclosed, which makes this wave directly replayable as an authorization-boundary audit map for any ZCS deployment. Band evidence varies per leg: some legs list 10.1.16/10.1.20, others 10.1.16/10.1.21; a critical override leg is explicitly retained unfixed in 10.1.17+. Per-leg band evidence is mandatory — one build check does not disposition this wave.

Authorized validation only

Test only owned ZCS labs or customer-approved instances. Proofs here are limited to route/attribute decision tables, owned-mailbox calendar canaries, synthetic documents, and lab-instrumented command/logging sinks. Never read other mailboxes, harvest real tokens/cookies, send attacker-selected mail at production MTAs, or touch other tenants' files or quotas.

Headline legs

1. Unauthenticated request-attribute override on /h/* — retained in "fixed" versions (GHSA-vffh-47xc-pr68 / CVE-2026-50053, 8.7)

RestFilter copies URL parameters — authentication token, target account, target item, dispatch state — into the servlet request attributes that the Classic Web Client consumes as trusted internal dispatch state. An unauthenticated request to /h/* can therefore populate the attributes the app believes only trusted dispatch sets. Confirmed vendor statement: ZCS 10.1.17 blocks the chained XSS by changing the document renderer but retains the request attribute override — the primitive itself survives the patch.

Operator rules:

  • URL-parameter → request-attribute copies are unauthenticated identity injection. Grep any Java mail-groupware filter chain for request.setAttribute fed from getParameter. Attribute stores are the classic confused-deputy channel: framework filters write them, downstream code never re-checks them.
  • Partial-fix disclosure is the strongest fingerprint available. A vendor explicitly stating "primitive retained, chain blocked" tells you the leg is waiting for a second bug on every later version. Re-test the attribute-override decision table (does /h/* still accept token/account/item parameters and act on them?) even on current builds.
  • Decision table to build pre-chain: anonymous request with self-issued valid token + own account/item ids → record whether Classic client honors them; then negative control with garbage token. Do not target other accounts even in the probe design.

2. Classic Web Client /h/rest packages parameter XSS → cross-user session execution (GHSA-rjjq-j274-2p2c / CVE-2026-10632, 8.7, fixed 10.1.17)

The document editor inserts package names from the packages parameter into script attributes without HTML encoding. Alone it is ordinary reflected XSS; chained with leg 1, an attacker with an ordinary Zimbra account supplies their own valid token and document identifiers in one URL, and a signed-in victim opening it runs attacker JavaScript under the Zimbra origin — mailbox read + authenticated actions as the victim. The chain is the story: low-priv + unauthenticated primitive = cross-user session compromise, and each leg is separately understated by its own CVSS lens. Search filter chains for any sink that renders request-attribute material into executable context (<script> attributes, inline handlers).

3. Delegated admin → global Zimlet config → proxy allowlist → same-origin attacker HTML (GHSA-q3fj-jg2r-pp4p / CVE-2026-93639, 8.7)

A delegated administrator with no explicit administrative grants can call ConfigureZimletRequest to overwrite global Zimlet configuration. The uploaded config can add an attacker-controlled domain to the proxy allowlist of the default class of service. Any signed-in user in that class who opens a crafted /service/proxy URL gets attacker-controlled HTML served under Zimbra's own origin — same-origin JavaScript reads mailbox data and acts as the victim.

Rules:

  • Enumerate admin SOAP requests reachable by delegated/partial admin roles, not just full admins. Delegated-administration surfaces routinely authorize the requester's realm but write global/default objects (same axis as cross-realm Keycloak/OpenAM legs on the Sept 21 precedence page).
  • Proxy allowlists are configuration write targets. Any config surface that steers a server-side proxy/fetch is a same-origin-content-injection primitive once writable — the Zimbra proxy fetches the allowlisted URL and renders it under the mail origin. Audit every "test this data-source/proxy/calendar URL" admin form against who may write it.
  • Chain precondition is just a victim click on an internal-looking URL — price this as one-click mailbox compromise once you have any delegated account.

Identity-binding failures in mail-native flows

Four legs share one root: the protocol identity field is trusted without verifying the transport sender owns it.

  • Organizer spoof, destructive (GHSA-77gq-8m6p-2c3g / CVE-2026-93627, 5.3): iCalendar CANCEL/REQUEST accepted without verifying the email sender is authorized as the ORGANIZER. Knowing the meeting UID + organizer address and supplying a non-outdated SEQUENCE lets an unauthenticated sender trash or rewrite a recipient's meeting — automatically at delivery, zero interaction.
  • Organizer spoof, additive (GHSA-x4qw-fpgg-g7x4 / CVE-2026-93629, 5.3): forged REQUEST inserts an invitation attributed to another user with attacker-selected details/reminders; nothing appears in the claimed organizer's calendar. Phishing that renders as trusted internal scheduling.
  • Attendee spoof via null identity (GHSA-p5gv-qg22-w4gq / CVE-2026-93628, 5.3): the REPLY processing path passes a null sender identity, so the attendee authorization filter is skipped entirely — classic null-means-bypass enforcement failure. Forge any attendee's participation status on the organizer's copy.
  • OAuth callback state not session-bound (GHSA-8c8x-hffr-47hm / CVE-2026-93631, 7.1, Network Edition): the OAuth extension callback binds neither state nor the authorization code to the initiating session; the destination mailbox is chosen from the browser's current auth cookie. A signed-in victim loading a callback carrying the attacker's fresh provider code gets a persistent attacker-controlled calendar data source; bidirectional CalDAV sync then discloses edited appointments outward.

Operator rules: on any groupware, run the iCalendar identity battery from an owned sending domain: CANCEL/REQUEST/REPLY against a known UID, correct and stale sequences, claimed organizer vs attendee addresses, SPF/DKIM-passing vs spoofed sender — decision-table the results. And on OAuth-link flows: complete the callback with your own provider code while a victim session is active in the same browser profile (lab) — if the link lands in their mailbox, state binding is absent.

Input-construction and sanitizer legs

  • LDAP filter injection via attribute names, not values (GHSA-xmgq-97vm-wxrh / CVE-2026-93636, 4.3): SearchCalendarResourcesRequest escapes search values but splices caller-supplied attribute names raw into the filter — inject conditions to recover otherwise-unreturned resource attributes: internal mailbox hostnames and mail transport routes. Rule: escaping coverage is per-grammar-position; fuzz the names/fields slot, not just the value slot. Still an infrastructure-disclosure oracle from an ordinary user.
  • SMTP CRLF injection in data-source testing (GHSA-m4x2-65hq-rqw6 / CVE-2026-115785, 4.3): after a successful POP3/IMAP test login, the JSON-SOAP-supplied emailAddress is inserted into an SMTP envelope command, CR/LF intact — pipelined commands give attacker-selected recipients/headers/body through the server's own MTA, no victim interaction. The "test this connection" admin/user forms are always first-class injection sinks; battery every test-field with CRLF shapes and watch the wire in a lab.
  • Sanitizer compares hostname pre-canonicalization (GHSA-34q7-82vg-cv73 / CVE-2026-93630, 4.3): DefangFilter / OWASP ActionAttributePolicy compare a form's action host against the request host before browser URL canonicalization, so a percent-encoded host survives defanging while the browser submits the form to the real Zimbra origin with the recipient's cookie. Deny-list matching on un-normalized input — same family as the Oct 6 Joomla whitespace-in-data-URI leg. Canonicalization-decision-table: sanitizer view vs browser view of the same URL string. (Not a CSRF-token bypass by itself — chain only to endpoints lacking token checks.)
  • Non-ASCII truncates the CSS sanitizer (GHSA-x7vx-vqqq-6frh / CVE-2026-93633, 5.4): DefangFilter processing a stylesheet containing non-ASCII characters appends the final ASCII segment unsanitized; an HTML document stored as application/x-zimbra-doc and shared via Briefcase retains external CSS url() references the normal sanitizer strips, rendering a credential-collection form in Zimbra's own skin under its own origin. Encoding-shape boundary cases (ASCII/non-ASCII segment splits) belong in every custom-sanitizer test battery.
  • Reflected XSS via zrememberme on the 2FA error path (GHSA-7vwh-h4vg-w75f / CVE-2026-93638, 6.1): the two-factor error display writes the parameter into a hidden input attribute unencoded. Note the vendor's own chain bound: the relogin path logs the session out first, so this prices as credential-capture (replacement login form), not session inheritance. Fuzz every error-display parameter, including the ones that only fire under MFA state.
  • Cross-account record forgery (GHSA-8563-28f8-ph7v / CVE-2026-93634, 4.3): FileSharedWithMeRequest bypasses delegated-account authorization and accepts attacker-selected file metadata/owner ids/declared sizes with no backing share — plant entries attributed to another real account with zero victim interaction. Write-path attribution forgery: check that every "shared with" insert validates the relation, not just the caller's session.
  • Quota accounting asymmetry (GHSA-w5xc-29c5-3j2h / CVE-2026-115786, 6.5): creating a recipient's shared-file record charges the file size to the recipient's mailbox-wide usage; revoking deletes the record and reverses folder counters only, never the mailbox-wide charge. Repeat share/revoke = silent cross-user quota exhaustion that blocks incoming mail. Rule: for every charge, verify the refund touches the same accounting table; create/delete counter-parity probes belong on any storage/quota product.

Local privilege escalation trio — passwordless sudo with unbounded arguments

Three legs share one shape: ZCS ships passwordless sudo entries whose argument lists the wrapper never constrains. If you have any zimbra shell on the box, root is a configuration problem.

  • MTA → root (GHSA-h6p2-5x3j-hpj8 / CVE-2026-50057, 7.8): sudo permits root postfix set-permissions with arbitrary arguments; zimbra controls daemon_directory in Postfix config (and the Postfix config dir is itself zimbra-writable, so locking postconf doesn't close it) → Postfix executes the attacker's post-install program as root.
  • mailboxd launcher → root (GHSA-ww99-8662-82q8 / CVE-2026-50056, 7.8): zmmailboxdmgr's passwordless sudo accepts JVM args; postpone Jetty's privilege drop, register an OnOutOfMemoryError command, force heap exhaustion at startup → command executes while the JVM is still root.
  • dnscache → root file read/write (GHSA-m5xr-vr49-p6gj / CVE-2026-93640, 7.8): sudo permits zmunbound with arbitrary args, forwarded to Unbound without restricting -c config path; a config that keeps root privileges and disables chroot turns include directives into root-readable file disclosure via parser errors and the logfile setting into root-owned file creation/append — password hashes, private keys, corrupted privileged config.

Audit rule for every appliance/product that ships sudoers entries: enumerate sudo -l, then for each allowed command ask which arguments the wrapper actually validates. Wrapper-scripts-forwarding-to-privileged-binaries with caller-controlled config/path/environment arguments are LPE by construction. Check the config files the privileged binary consumes for writability by the low-priv user — the argument allowlist is not the boundary when the config dir is writable. On a pentest, zimbra (or any service account) foothold on a ZCS box is a reportable root-path finding on all three legs; run them only in lab-approved windows since the mailboxd leg disrupts the service.

Wave-level operator notes

  • Seventeen legs, four precondition tiers: unauthenticated (/h/* override, login XSS, organizer/attendee spoofs, defang bypass), ordinary-authenticated (LDAP oracle, SMTP CRLF, CSS doc, quota drain, /h/rest XSS), delegated-admin (Zimlet proxy), local-service-account (three sudo LPEs). Map your access level to the ladder before testing; the wave reads as a complete ZCS audit tree.
  • Fix-state per leg, not per build: 10.1.17 blocks only the XSS renderer half of legs 1+2; leg 1 explicitly persists. Record legs against builds in a decision table, never "10.1.17 = safe."
  • Same-product full re-sweep pays again: third Zimbra wave this quarter (Aug 21 SNMP-notification SMTP cmdi KEV already on the wiki); each wave exposes new endpoint families. When a vendor publishes one detailed advisory, expect the rest of their audit queue on a schedule.

Tracked with this wave, not published

Three thin MCP npm-package RCE singles (@rog0x/mcp-docker-tools handleImageList, @rog0x/mcp-git-tools resolveBaseBranch, @justice8096/sagemath-mcp-server escapeShellString/sanitizeCode — MCP tool-argument shell class, known); serialize-javascript 7.0.1–<7.1.3 `