Skip to content
← All posts
· 12 min read· By

Zammad flaws CVE-2026-102489 and 102490: secure your server now

Two actively exploited Zammad flaws: secure the logs, switch the package source, update to 7.2.1 and contain the still unpatched root escalation.

Since 2 October 2026, two Zammad flaws have been listed in the US agency CISA's catalog of actively exploited vulnerabilities; the German BSI rates them as critical in its warning. On Zammad up to 6.5.4, CVE-2026-102489 leads from a hijacked session to code execution as the zammad user. CVE-2026-102490 elevates exactly that user to root, and according to the Zammad advisory there is no fix for it yet (as of 8 October 2026). If you self-host Zammad, secure the evidence now, update to 7.2.1 and lock down the server. Package installations face an extra trap: the old package source no longer ships any version from 7.1 onwards, and apt upgrade does not tell you.

Two flaws, one attack chain

The Dutch DIVD analysed the flaws after attackers had used them on 21 September 2026 to break into DIVD itself. According to DIVD, Zammad was notified on 24 September, the Zammad advisory followed on 5 October and version 7.2.1 on 6 October.

CVE-2026-102489: hijacked session, attacker code

According to NVD, versions 6.3.0 to 6.5.4 are vulnerable to a session hijack that ends in code execution as the zammad user. The bug is also present in 7.0.0 to 7.1.2, but not exploitable there because of changes in the underlying framework. Zammad confirms: only 6.5 and older versions can be exploited, and they no longer receive security updates. The code changes are included in 7.2.0.

CVE-2026-102490: from service account to root

According to NVD, the second flaw affects all versions up to the latest alpha. Zammad classifies it as a local privilege escalation that cannot be exploited remotely on its own, and points to a confirmed vulnerability in packager.io, the service used to build the Zammad packages. The combination is what makes it dangerous: anyone who runs code as zammad via the first flaw has exactly the local access the second one requires.

+----------------------------------------------------------+
| 1. Attack over the network                               |
|    |                                                     |
|    |  CVE-2026-102489 (6.3.0 to 6.5.4)                   |
|    |                                                     |
| 2. Code runs as user "zammad"                            |
|    |                                                     |
|    |  CVE-2026-102490 (all versions)                     |
|    |                                                     |
| 3. root on the server                                    |
+----------------------------------------------------------+
CVE-2026-102489 CVE-2026-102490
Impact code execution as zammad privilege escalation to root
Precondition access over the network local access to the server
Exploitable 6.3.0 to 6.5.4 all versions
Fix code changes from 7.2.0 none (as of 8 October 2026)
CVSS 4.0 (DIVD) 8.7, chained 9.4 8.5, chained 9.4
CVSS 3.1 (NVD) 9.8 9.8

Taking stock: version and package source

Which version is running?

The quickest way is the admin area under System > Version. On the command line, the package manager and Docker help:

# package installation on Debian or Ubuntu
apt-cache policy zammad
# package installation on RHEL or SLES
rpm -q zammad
# Docker Compose: tags of the running images
docker compose images

Where do the packages come from?

Since Zammad 7, the packages live at a new address, and according to the 7.1 release notes the old repositories only serve versions prior to 7.1 until they are shut down. If you set up your server before 26 March 2026 following the instructions of the time, it probably still pulls its packages from dl.packager.io. Here is how to check:

grep -r "packager.io" /etc/apt/sources.list.d/ /etc/yum.repos.d/ /etc/zypp/repos.d/ 2>/dev/null

A cross-check on 8 October 2026 in a fresh Ubuntu 24.04 container, once with each source, shows the difference:

# old source: dl.packager.io
$ apt-cache policy zammad
zammad:
  Installed: (none)
  Candidate: 7.0.2-1781669454.9dc15acf.noble

# new source: go.packager.io
$ apt-cache policy zammad
zammad:
  Installed: (none)
  Candidate: 7.2.1-1791411395.f5b1a3e.ubuntu24
old source new source
Host dl.packager.io go.packager.io
Release file (Ubuntu 24.04) at measurement time 2026-06-25 2026-10-07
Newest version 7.0.2 7.2.1
Signing key B6D583CCBD33EEB8 54109AEE4C3EC117

A server on the old source therefore considers itself up to date at 7.0.2. CVE-2026-102489 is not exploitable there in practice, but the 27 flaws that Zammad closed with 7.2.1 according to its GitHub advisories remain open, two of them critical. A future fix for CVE-2026-102490 will not arrive through this source either.

Secure the evidence first, then update

Why not simply update right away?

The obvious reflex is: install the update, carry on. For instances that ran 6.5.x or older and were reachable from the internet, that order is wrong, for three reasons. First, an update replaces the code under /opt/zammad, but it removes nothing an attacker with root privileges has placed elsewhere. Second, docker compose up -d with a new image replaces the containers, and their logs disappear with them; in a test with a throwaway stack, docker compose logs afterwards returned only the lines of the new container. Third, on package installations logrotate deletes old logs after 14 days according to the Zammad documentation, so on 8 October they only reach back to about 24 September. Hence: take Zammad off the network immediately (firewall or reverse proxy), secure the logs and a backup, check, and only then update.

Searching the logs with the DIVD check script

On its case page, DIVD provides a script that searches Zammad and nginx logs (compressed ones too) for ERROR lines containing session data. Version 2 of the script, however, has Windows line endings, and under dash, the /bin/sh of Debian and Ubuntu, it aborts immediately:

$ sh cve-2026-102489_ioc_check_script_v2.sh
cve-2026-102489_ioc_check_script_v2.sh: 7: set: Illegal option -

Behind the hyphen sits an invisible carriage return: set -u arrives as set -u\r. In addition, the script always ends with exit code 0, even if it did not find a single log file. So remove the carriage returns first and evaluate the output, not the exit code:

sed 's/\r$//' cve-2026-102489_ioc_check_script_v2.sh > zammad-ioc-check.sh
# package installation: pass both possible log paths and nginx
sudo sh zammad-ioc-check.sh /var/log/zammad /opt/zammad/log /var/log/nginx
# Docker Compose: export the logs to a file first
docker compose logs --no-color > zammad-docker.log
sh zammad-ioc-check.sh zammad-docker.log

If the script reports error: no log files found, the path is wrong; that is not an all-clear. It also only looks for traces of CVE-2026-102489 and contains no indicators for the root escalation.

A hit: rebuild instead of repair

If the check finds something, no update will help: with the root flaw open, the server can no longer be trusted. Restore the backup on a new server (according to the Zammad documentation, easiest with the same version as before), update to 7.2.1 there, review the admin accounts and rotate all credentials: email channels, API tokens, admin passwords. The downtime cost calculator puts a figure on what a day without a helpdesk costs.

Updating to 7.2.1 for packages and Docker Compose

What must be in place before the jump from 6.5

Zammad advises not to skip any major version when updating; going from 6.5 to 7.2.1 skips none. Before that, however, the following must be in place:

Component Requirement for 7.2.1
Database PostgreSQL 15 or newer, migrate MySQL and MariaDB first
Elasticsearch 8.15 or newer, but below 10
Redis 7 or newer
nginx proxy_http_version 1.1; in the main location as well

If you still use MySQL, you cannot move to 7 right away; that leaves the second option DIVD names: take the instance offline.

Package installation: switch the source, then update

The backup script needs a one-time configuration (rename config.dist in /opt/zammad/contrib/backup/ to config), and the result then belongs off the server, for example encrypted with Restic to S3 or Backblaze B2. The source switch follows the Zammad documentation, here for Ubuntu 24.04:

sudo systemctl stop zammad
# secure the logs separately, missing paths are skipped
sudo tar czf /root/zammad-logs-pre-update.tar.gz --ignore-failed-read /var/log/zammad /opt/zammad/log /var/log/nginx
sudo /opt/zammad/contrib/backup/zammad_backup.sh
# remove the old source and the old key (the grep above shows the file names)
sudo rm /etc/apt/sources.list.d/zammad.sources
sudo rm /etc/apt/keyrings/pkgr-zammad.gpg
# add the new key and the new source
sudo curl -fsSL "https://go.packager.io/srv/deb/zammad/zammad/gpg-key.gpg" \
  -o /usr/share/keyrings/zammad.gpg && sudo chmod 644 /usr/share/keyrings/zammad.gpg
sudo curl -fsSL "https://go.packager.io/srv/zammad/zammad/stable/installer/ubuntu/24.04.list" \
  -o /etc/apt/sources.list.d/zammad.list
sudo apt update
sudo apt-mark unhold zammad
sudo apt upgrade zammad

If you only replace dl. with go. and keep the old key, apt update fails:

Err:1 https://go.packager.io/srv/deb/zammad/zammad/stable/ubuntu 24.04 InRelease
  The following signatures couldn't be verified because the public key is not available: NO_PUBKEY 54109AEE4C3EC117
E: The repository 'https://go.packager.io/srv/deb/zammad/zammad/stable/ubuntu 24.04 InRelease' is not signed.

The cause: the new source is signed with a different key (54109AEE4C3EC117) than the old one (B6D583CCBD33EEB8). Without the new key, apt rejects the source.

Docker Compose: check VERSION in the .env

The Compose file contains image: ${IMAGE_REPO:-ghcr.io/zammad/zammad}:${VERSION:-7.2.1-0000}. If your .env still contains a value such as VERSION=6.5, the stack stays vulnerable after git pull; on 8 October, the 6.5 tag even pointed only to 6.5.2-94. If you come from an older stack, the release notes of the Compose stack list these stumbling blocks:

Stack version Change
v14.0.0 new Elasticsearch image, fix the volume permissions once
v15.0.0 Elasticsearch 9 and Redis 8, CPU with SSE4.2 required
v16.0.0 no automatic backup on restart anymore
v17.0.0 Docker Compose 2.23.1 or newer
cd zammad-docker-compose
grep '^VERSION' .env
# secure logs and a backup before the containers are replaced
docker compose logs --no-color > zammad-logs-pre-update.log
docker compose run --rm --env BACKUP_ONCE=true --env BACKUP_ON_START=true zammad-backup
git pull
docker compose pull
# only when jumping from a stack before v14.0.0 (new Elasticsearch image)
docker compose run -it -u0 zammad-elasticsearch chown elasticsearch -Rv /usr/share/elasticsearch/data
docker compose up -d
docker compose images

Before the git pull, the old image is still running. Images of the 6.5 and 7.0 series and early 7.1 builds do not know BACKUP_ONCE: they back up immediately (early 7.1 builds only thanks to BACKUP_ON_START=true) and then wait for the next scheduled run. Stop the command with Ctrl+C after backup finished :); the backup stays in the backup container's volume.

If you pin VERSION, you are responsible for patch updates yourself according to .env.dist; new tags are reported, for example, by Diun instead of Watchtower.

After the update: FQDN and search index

Among other things, 7.2.1 fixes GHSA-23hj-h8w6-rgm3: scripts in the browser could read the session identifier. Since then, the http_type and fqdn settings must match the URL users open; for one user in the Zammad forum, tickets could otherwise no longer be saved. Check both values and rebuild the search index, as the breaking changes for 7.0 and 7.2 require:

# package installation
zammad run rails r "p Setting.get('fqdn'); p Setting.get('http_type')"
zammad run rake zammad:searchindex:rebuild
# Docker Compose
docker compose run --rm zammad-railsserver bundle exec rails r "p Setting.get('fqdn'); p Setting.get('http_type')"
docker compose run --rm zammad-railsserver bundle exec rake zammad:searchindex:rebuild

Containing CVE-2026-102490 until a fix ships

Who can get a shell?

Zammad recommends restricting access to the server to trusted administrators. Check which accounts have a login shell, who may use sudo and who logged in last:

getent passwd | awk -F: '$7 !~ /(nologin|false)$/ {print $1": "$7}'
# RHEL: group wheel instead of sudo
getent group sudo
last -n 20

SSH does not belong on the open internet; a WireGuard tunnel between the admin machine and the server closes the port to the outside.

Sessions, tokens and two-factor

After a flaw that allowed sessions to be hijacked, all sessions should end. According to the documentation, maintenance mode (System > Maintenance) logs off agents and customers. Admin sessions are not affected by it; delete those one by one under System > Sessions. If you switch off token access under System > API, all personal access tokens stop working for as long as the switch is off (they are not deleted); OAuth2 tokens are not affected. Then enforce two-factor authentication via the "Enforced for user roles" setting. That is only reliable from 7.2.1: up to 7.2.0, the two-factor check could be bypassed through the email verification flow according to GHSA-f3qr-94c2-7mx2.

In brief: questions about the Zammad flaws

Are Zammad 7.0 and 7.1 still at risk?

From CVE-2026-102489, not in practice according to Zammad and NVD. CVE-2026-102490, however, affects all versions, and all 27 flaws closed with 7.2.1 also affect 7.0 and 7.1. The target is therefore 7.2.1.

My scanner reports 7.2.1 as not affected. Is that right?

Not for CVE-2026-102490. The NVD description names all versions, but the machine-readable version range (CPE) ends before 7.1.0. Scanners that only evaluate the CPE data therefore report 7.1 and newer as clean, even though there is no fix.

Does Docker protect against the root escalation?

That cannot be established: neither the advisory nor the NVD description distinguishes between installation types, and the NVD configuration even lists Docker as a platform. That the 7.2.1 image runs with UID 1000 instead of root according to its configuration is no all-clear.

Do I have to report an incident?

If attackers got hold of tickets, that almost always involves personal data. Whether a GDPR reporting obligation applies is something to clarify with your data protection officer. For the analysis and a clean rebuild, our IT support for SMBs can help.

Tracking the fix: sources and documentation

Advisories and warnings

The fix status of CVE-2026-102490 is documented in the Zammad advisory, and the flaws closed with 7.2.1 are listed in Zammad's GitHub advisories. Add to that the NVD entries for CVE-2026-102489 and CVE-2026-102490, the CISA alert of 2 October, the BSI warning WID-SEC-2026-3694 and the DIVD case page with the check script. The post on the nginx flaw CVE-2026-42945 applies the same approach to a web server.

Zammad documentation

The relevant pages for the implementation are Updating Zammad, Host Upgrade and Repository Migration, the software requirements and Backup and Restore for Docker Compose.

Note: The articles on this blog are produced with the help of AI and are editorially reviewed before publication. Editorial responsibility lies with Emre Yurtbay (see the Impressum).

Discuss your project