Critical CVE Response

An AI Agent Chained Two Zammad Zero-Days to Root. Neither Has a Vendor Advisory.

Dark cyberpunk illustration of an unattended service counter at night, its metal shutter buckled open with amber light spilling across a wet floor, and a thin cyan line running from the counter into a dark corridor of server racks.

The scripts the attacker left on the Dutch Institute for Vulnerability Disclosure's network carried comments explaining themselves. One passage assured whoever read it that the step it described did not count as phishing. The automation was writing justifications for its own actions as it went. DIVD published that detail in its own incident case, and described the operator as an agent that chose each next step itself, at speed, on sloppy logic. It is the plainest published artifact so far of an intrusion driven by software rather than a person.

The way in was a helpdesk. DIVD runs Zammad, the open-source ticketing system, and on September 21 the attacker chained two then-unknown flaws in it. CVE-2026-102489 is a session hijack that yields code execution as the zammad service account. CVE-2026-102490 escalates that account to root. CISA added both to the Known Exploited Vulnerabilities catalogue on October 2 with a remediation date of October 5. Zammad GmbH has published no advisory for either one.

Only self-hosted installs are in scope

This is a self-hosted problem. If your service desk is Zendesk, Freshdesk, Jira Service Management, or Zammad's own hosted plan, the host described here is not on your network and you can stop reading. The population at risk is the package, Docker and source installs, which put the application, its database and its Unix service account on a machine you operate. That machine is what the second flaw escalates on.

Zammad is free to self-host, which is exactly why it turns up in small IT departments and inside managed service providers, often behind a public hostname so customers can open tickets. DIVD scanned for reachable instances and started notifying their owners on September 26, so the exposed population is large enough to be worth a scan. Ask the simpler question first: does anyone in your organisation know which host runs your ticketing system, and whether it answers from the internet? In the environments I assess, the helpdesk is usually absent from the asset inventory, because it was stood up by the IT team for the IT team.

The October 5 date binds federal civilian agencies under BOD 26-04. For a contractor, an insured business, or anyone answering a security questionnaire, it arrives as an expectation instead of an order. The date is the least useful part of a KEV entry anyway. The useful part is the claim underneath it: somebody has already been attacked this way.

What DIVD found in its own ticketing system

DIVD is a volunteer organisation that reports vulnerabilities to other people. Its case file for these two CVEs exists because its own helpdesk was the entry point, and it wrote the sequence down.

  • September 21 - the chain is used against DIVD's Zammad instance. Session hijack to code execution as zammad, then escalation to root, then access to further internal services.
  • September 22 - the breach is discovered. DIVD blocks access to its datacenter and brings in a third-party response firm.
  • September 22-23 - analysts reproduce both flaws from the evidence.
  • September 23 - Zammad 7.2.0 ships, on its own schedule, as a minor release.
  • September 24 - DIVD reports both issues to Zammad.
  • September 26 - DIVD publishes a limited disclosure, scans for public instances, and begins notifying owners.
  • September 30 - both CVEs appear in NVD.
  • October 2 - CISA adds both to KEV, due October 5, with the forensic triage requirement set.

What the attacker took was volunteer contact data, including DIVD email addresses. Network segmentation stopped the spread short of anything else. DIVD found no link to a known public threat actor and no sign that the operator went hunting for a specific prize. It described the intrusion as loud and very messy, which is consistent with an agent optimising for progress rather than for staying quiet.

The two accounts do not agree, and the gap is the problem

Read the published record and you get two different vulnerabilities. DIVD assigned the CVE IDs as the numbering authority, so its version ranges are what landed in NVD: CVE-2026-102489 is exploitable in 6.3.0 through 6.5.4 and present but not exploitable in 7.0.0 through 7.1.3, and CVE-2026-102490 covers every release from 1.5.0 to 7.1.0-alpha. Zammad's position, posted in its community forum on October 1, is that 6.5 and older are the affected branch and that "Zammad 7.0 and later are not affected" by the first flaw. On the second, the vendor wrote: "We cannot verify a claim we have not been shown. This means we cannot confirm the vulnerability, its scope or the affected versions at this time." It said it had formally asked DIVD for the technical detail, and it called a two-day gap between report and public disclosure irresponsible.

One Zammad developer, after receiving details, added the most operationally useful sentence in the whole exchange: "This issue cannot be exploited remotely on its own. An attacker would already need access to your server."

That sentence corrects a number you will otherwise mis-read. Both CVEs are scored with Attack Vector: Network. DIVD rates each at CVSS 4.0 9.4; NVD rates each at CVSS 3.1 9.8 with no privileges and no user interaction required. Yet the NVD text for CVE-2026-102490 describes a local escalation, in its own words enabling "the local zammad user to escalate privileges to root." A network attack vector on a local privilege escalation is an artifact of scoring the chain rather than the component, and a triage process that sorts by base score will treat a post-access bug as a pre-authentication one. Score the pair as what it is: a remote entry point in one branch, plus a local escalation that makes any foothold on that host a root foothold.

The gap that matters more is the one in the vendor's own publication stream. Zammad retired website advisories at ZAA-2026-07 in April 2026 and moved them to GitHub, and it told users to watch GitHub for updates on CVE-2026-102490. The published advisory list there still ends on August 25, 2026. Neither CVE has an entry. So the compliance clock expires today against a fix that no vendor advisory describes, and the version number on your host is a claim from one party that the other party disputes. Upgrade anyway, to the highest version you can run, and then stop trusting the version number as your only evidence.

Find the instance and read its version

Zammad resolves its own version by reading a VERSION file from the application root at runtime, so the file and the running code agree. For a package install the root is /opt/zammad. The documented console wrapper runs a one-liner without opening an interactive shell.

# Package install, on the host.
cat /opt/zammad/VERSION
zammad run rails r 'p Version.get'

# Docker-compose install.
docker compose exec zammad-railsserver cat /opt/zammad/VERSION

# From outside, confirm whether it answers at all, and to whom.
curl -sS -o /dev/null -D - -m 10 https://helpdesk.example.com/ | head -1

Record three facts per instance: the version string, whether the login page is reachable from an untrusted network, and the date of the last upgrade. The third is the one that decides the next section.

Session rows outlive the code that created them

This is the part the version debate buries, and it follows directly from the weakness class. CVE-2026-102489 is catalogued as session fixation, CWE-384. Zammad stores sessions in its database, through Rails' ActiveRecord session store, as rows in a sessions table backed by a Session model. Replacing the application code does not delete those rows. A session an attacker fixed before your upgrade keeps authenticating after it, through code that no longer contains the bug.

Clear the table. Every user signs in again, which is a cheap afternoon of complaints next to an authenticated attacker who survived your remediation. Then check the credential that does not live in that table at all: Zammad's persistent API tokens, which are rows in a separate Token model and are unaffected by anything you do to sessions.

# zammad run rails c   (interactive console, package install)

# 1. How many sessions exist, and how far back do they go.
p Session.count
p Session.order(:updated_at).first&.updated_at

# 2. Invalidate all of them. Everyone re-authenticates.
Session.delete_all

# 3. Persistent API tokens survive step 2. Review them by age.
Token.where(action: 'api', persistent: true)
     .order(:created_at)
     .each { |t| p [t.id, t.user_id, t.created_at] }

# 4. Accounts holding admin rights, so you can spot one you did not create.
Role.find_by(name: 'Admin').users.each { |u| p [u.id, u.login, u.created_at] }

Any token created in a window you cannot account for gets deleted, not investigated. The same rule applies to an admin account nobody remembers provisioning.

What to look for if the instance was reachable

The KEV entries for both CVEs carry CISA's forensic triage requirement, which is the catalogue's way of saying that patching alone does not answer the question. Two checks give the best return, and both exploit the same structural fact: the zammad account exists to run one Ruby application and nothing else, so anything else it did is evidence.

# Files the service account wrote outside its own tree. The highest-signal
# check for CVE-2026-102489, because the application never needs to.
find / -xdev -user zammad -newermt '2026-09-01' -type f \
     -not -path '/opt/zammad/*' -not -path '/proc/*' -not -path '/sys/*' \
     2>/dev/null

# Shell and download tooling in the application's own log and service journal.
grep -Ein 'base64 -d|/bin/sh|/bin/bash|curl |wget ' /opt/zammad/log/production.log
journalctl -u zammad --since 2026-09-01 | grep -Ei 'sh -c|curl |wget |nc '

# Root-level persistence, which is what CVE-2026-102490 buys.
find /etc/systemd/system /etc/cron.d /etc/cron.daily -newermt '2026-09-01' -type f
ls -la /root/.ssh/authorized_keys 2>/dev/null
stat -c '%n %y' /root/.ssh/authorized_keys 2>/dev/null

Adjust the dates to your own exposure window rather than copying mine. If the host has been internet-facing and unpatched since the spring, start in the spring. A clean result from these checks proves little by itself, and it is still worth the ten minutes, because a positive result changes the job from patching to incident response in one command.

If any of it comes back dirty

Treat the host as root-compromised and rebuild it rather than cleaning it. The escalation flaw has no vendor advisory, so you have no way to establish which root-level change was the attacker's and which was the installer's. Preserve the disk image before you rebuild, because the ticket data is also the record of what the attacker could read. Rotate every secret the application held: mail account credentials, LDAP or Entra bind accounts, outbound webhook secrets, and any API token you found in step 3 above.

Empty the session table before you call the upgrade done

If you self-host Zammad, today's work is three commands long: read the version, upgrade to the current release, and delete every session row. The third step is the one almost nobody will do, and it is the only one that addresses what a session fixation flaw actually leaves behind. Then run the two triage checks above, because the version ranges published by the reporter and the vendor do not match and your version string alone cannot tell you whether you were reachable in the window that mattered.

Carry one thing past this CVE pair: what counts as a production system. A helpdesk is a database of your customers' problems, reachable from the internet, running as a service account on a host nobody audits. It earned its place in the asset inventory before this week. We help organisations build and test the response playbook for exactly this case, the one where the vendor advisory you need does not exist yet, and the decision has to be made from evidence on the host instead.

Need an incident response plan before the next attack?

We help organizations build and test incident response playbooks. Book a session with our team.