Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-20217 — Reproduces ZendTo unauthenticated ClamAV RCE and root privilege escalation in an authorized lab, with pinned Docker target, fail-closed verification, nonce-bound output, and cleanup. | Kitploit
Tools/GitHubGitHub/securifera/cve-2026-20217
Privilege EscalationVulnerability AnalysisExploitationWeb Application ExploitationPost-ExploitationWeb SecurityPenetration TestingRed Teaming
GitHubsecurifera/cve-2026-20217

CVE-2026-20217

Reproduces ZendTo unauthenticated ClamAV RCE and root privilege escalation in an authorized lab, with pinned Docker target, fail-closed verification, nonce-bound output, and cleanup.

View Repository
11523 days agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

ZendTo 6.15-8 / ClamAV exact-profile RCE and root-variant reproduction

This directory is a standalone, authorized-lab reproduction bundle for the no-account ZendTo-to-ClamAV code-execution chain and its separate default- profile Smarty/root-cron continuation. It includes:

  • the complete runnable PoC dependency closure;
  • scripts and provenance for constructing the Docker target topology;
  • a Docker Compose definition matching the tested systemd/socket lifecycle;
  • fail-closed image, package, binary, configuration, CVD, DAC, cron, PHP, MAC, and filesystem-semantic checks; and
  • helpers for the one lab-only verification capability and for cleanup.

The initial PoC executes a caller-selected command as the stock clamav service account. The optional variant continues that no-account foothold to a caller-selected command as root. Both default to id, reject the literal /root/flag path, and return nonce-bound output over HTTPS.

The vulnerable group, directory, Smarty, and root-cron relationships are ZendTo Debian package/installer defaults. Root reachability is nevertheless environment-conditioned: this exact positive fixture uses an ext4-backed /var/zendto volume, functional PHP CLI FFI/POSIX/exec, and an unconfined clamd process. Enforcing AppArmor/SELinux or incompatible filesystem semantics can block the continuation on another otherwise stock installation.

Use this only on the included disposable fixture or another system you are explicitly authorized to test.

What is pinned

The authoritative exact target is a local, intentionally unversioned artifact at:

root@kitploit:~
image/zendto-installer-systemd-debian12.tar.zst

Git ignores Docker image archives. Before exact regression, place an authorized local copy at that path; scripts/setup.sh requires its recorded SHA-256 and image ID. source-recipe/README.md documents how the installer-derived image was built and captured. A new build from live package repositories is useful for topology testing but is not presumed byte-identical to the pinned target.

The setup and container entrypoint reject any mismatch from the following profile:

The complete values are in PROFILE.json.

This is stronger than pinning only the ClamAV package version. The allocator construction also depends on glibc, CVD-driven parser traffic, worker settings, and the exact target library. FreshClam is disabled in the captured image, and startup fails if daily.cvd has drifted or a daily.cld has appeared.

The currently long-running research container in the parent workspace is not the clean fixture: later testing disabled its external-sender form and updated its daily database. Those changes are intentionally excluded here. This bundle uses the clean baseline against which native execution was demonstrated.

Host requirements

  • x86-64 Linux with Docker Engine and Docker Compose v2
  • permission to run privileged containers
  • cgroup v2 and /sys/fs/cgroup
  • zstd, Python 3 with venv, a C compiler, file, and GNU readelf
  • enough memory for ClamAV's native parsers without a tight container limit
  • at least 1 GiB free disk for the local image artifact and running target

The target shares the host kernel and ASLR implementation. Kernel-dependent mapping behavior therefore remains a portability variable.

Only one systemd fixture should share the host cgroup namespace at a time. On the original research machine, stop the older fixture without deleting it:

root@kitploit:~
docker stop zendto-installer-systemd-native

It can later be restored with docker start zendto-installer-systemd-native.

Start the exact target

From this directory:

root@kitploit:~
./scripts/verify-bundle.sh
./scripts/setup.sh

The setup script:

  1. verifies the immutable source manifest and local image SHA-256;
  2. loads and retags the exact image without contacting a registry;
  3. creates .venv with the pinned PoC Python dependencies;
  4. rebuilds the small local PESpin helper for the operator's libc;
  5. checks the local libclamav artifact and all target gadget windows;
  6. validates the Compose model;
  7. creates a fresh named /var/zendto volume and starts systemd; and
  8. verifies package versions, target hashes, CVDs, Apache, clamd, and socket activation; and
  9. proves the default root-variant prerequisites, including a disposable renameat2(RENAME_EXCHANGE) operation performed as clamav.

The default endpoints are loopback-only:

root@kitploit:~
http://127.0.0.1:18084/
https://127.0.0.1:18447/

The certificate is self-signed. The PoC deliberately uses verify=False for every HTTP request. Different loopback ports can be selected before setup:

root@kitploit:~
export ZENDTO_HTTP_PORT=19084
export ZENDTO_HTTPS_PORT=19447
./scripts/setup.sh

Use the same environment variables for later Compose/helper commands.

Check the target at any time:

root@kitploit:~
./scripts/verify-target.sh
docker compose ps

The CAPTCHA/email boundary in this fixture

The clean installer baseline has:

root@kitploit:~
allowExternalUploads = TRUE
confirmExternalEmails = TRUE
captcha = google

Its CAPTCHA and mail values are installer placeholders, so the untouched archive cannot actually deliver the public verification email. Native regression testing represented only that completed application step with an equivalent external-sender AuthData row. The target code, packages, scanner, permissions, and exploit path were not patched.

Create that one lab row and a mode-0600 token file with:

root@kitploit:~
./scripts/mint-lab-auth.py [email protected]

The token value is suppressed and stored at:

root@kitploit:~
.lab/upload-auth-token.txt

This local helper is not a claim that an external-upload-disabled deployment can be exploited remotely. If the public external-sender form is disabled, the PoC correctly stops before ClamAV even when a token file is supplied.

On a normally configured authorized deployment, omit --external-auth-token-file: manually open the verification page, solve the CAPTCHA, receive the target-generated message at an attacker-controlled mailbox, and paste its URL/token at the hidden prompt.

1. Remote-only enumeration

This mode sends no CAPTCHA, email, capability, upload, login POST, or scanner content. --skip-clamd-tcp-probe also suppresses the optional read-only native TCP/3310 check:

root@kitploit:~
mkdir -p -m 700 work

.venv/bin/python poc/zendto_6_15_8_unauth_clamav_rce.py \
  https://127.0.0.1:18447 \
  --enumerate-only \
  --skip-clamd-tcp-probe \
  --fingerprint-json work/fingerprint.json

Enumeration still produces ordinary web access logs and can initialize normal application caches. Its Debian/Ubuntu Apache-layout result does not attest the native package, Build ID, libc, CVDs, socket policy, or MAC state. The local Docker verification script supplies that evidence for this fixture.

2. Capability and recipient dry run

This validates the lab bearer and recipient policy but submits no file or scanner content:

root@kitploit:~
.venv/bin/python poc/zendto_6_15_8_unauth_clamav_rce.py \
  https://127.0.0.1:18447 \
  --sender-email [email protected] \
  --recipient [email protected]. \
  --external-auth-token-file .lab/upload-auth-token.txt \
  --probe-only \
  --no-open-browser

[email protected]. matches the deliberately unusual test. internal-domain value entered when this installer fixture was created. A custom real deployment may require an actual authorized recipient.

3. Destructive end-to-end code execution

Warning: this is not a benign scanner check. Before attempt one, the remote mode sends EICAR, deliberately crashes clamd with the PESpin discriminator, proves socket-triggered recovery, and runs two terminal code-page probes with additional crash/recovery cycles. Rejected mappings intentionally kill clamd.

Use a fresh work directory and a harmless command:

root@kitploit:~
run_dir="work/run-$(date +%Y%m%d-%H%M%S)"
mkdir -m 700 "$run_dir"

.venv/bin/python poc/zendto_6_15_8_unauth_clamav_rce.py \
  https://127.0.0.1:18447 \
  --sender-email [email protected] \
  --recipient [email protected]. \
  --external-auth-token-file .lab/upload-auth-token.txt \
  --oracle-conditioned-attempts 72 \
  --command 'id; uname -a' \
  --workdir "$run_dir" \
  --keep-workdir

The preferred conditioned route tests for the useful nibble-8 mapping before submitting one corruption carrier. The current three-layout aggregate is a later lifecycle correction; its historical one-layout oracle and same-process RCE stages are documented separately in docs/.

The blind fixed-nibble-6 fallback is available explicitly:

root@kitploit:~
.venv/bin/python poc/zendto_6_15_8_unauth_clamav_rce.py \
  https://127.0.0.1:18447 \
  --sender-email [email protected] \
  --recipient [email protected]. \
  --external-auth-token-file .lab/upload-auth-token.txt \
  --restart-resample-attempts 72 \
  --command 'id; uname -a' \
  --workdir work/blind-run \
  --keep-workdir

Under an independent uniform nibble model, 47 fresh mappings are about 95% and 72 about 99%. Those are geometric mapping probabilities, not end-to-end reliability guarantees. Each rejected mapping takes at least the 12-second recovery delay plus the enforced 40-second worker-retirement period, excluding scan time. A 72-attempt run can exceed an hour.

The result is not read from a callback or webshell. The DSO creates a separate 30-minute output-only AuthData bearer, executes /bin/sh -c as clamav, and returns bounded stdout/stderr through an anonymous ZendTo page. Success requires one framed result with all of the following consistent:

  • zendto_unauth_clamav_rce_result=1
  • exact run ID and command SHA-256
  • exact libclamav Build ID marker
  • reported uid/euid/gid
  • completion marker and exit/timeout/signal state

Missing, duplicate, truncated, or mismatched proof is a failed run.

4. Optional no-account continuation to root

Run this only after ./scripts/verify-target.sh reports both [target] and [privesc-target] success. The wrapper first performs the same no-account ClamAV stage, requires its exact bounded arming proof, then waits for the unchanged stock root cron consumer:

root@kitploit:~
.venv/bin/python poc/zendto_clamav_to_root_smarty_privesc.py chain-unauth \
  https://127.0.0.1:18447 \
  --sender-email [email protected] \
  --recipient [email protected]. \
  --external-auth-token-file .lab/upload-auth-token.txt \
  --oracle-conditioned-attempts 72 \
  --root-command 'id; uname -a' \
  --keep-workdir

The token-file option is only the fixture's completed-email substitute. On an authorized public workflow, omit it and manually complete the CAPTCHA/email prompt. No ZendTo username or password is used by chain-unauth.

The root stage is deliberately separate even though the wrapper automates its handoff. It:

  1. uploads a nonce-bound PHP armer through unauthenticated savechunk.php;
  2. supplies its SHA-256-gated command to the initial ClamAV RCE;
  3. atomically exchanges a cloned Smarty compile directory as clamav;
  4. lets the stock minute-25 root cleanup job load the guarded wrapper;
  5. restores the genuine compile directory before command execution; and
  6. downloads /zendto-root-result-<nonce>.json with verify=False, requiring euid=0, the exact command hash, restore marker, and completion nonce.

Cron may make the final poll wait almost an hour; the default timeout is 3900 seconds. The root command is limited to 1024 bytes and 30 seconds, with at most 1 MiB each of stdout and stderr. Its nonce-addressed JSON is intentionally web readable in this disposable lab, so use only harmless test output.

For manual orchestration, emit prints the exact command to pass to the no-account PoC's --command; after the arming proof, use poll with the printed run ID and SHA-256 of the exact root command. See docs/SMARTY_ROOT_BRIDGE.md for the construction and audit transcript.

This variant is expected to stop safely when a prerequisite is missing. Do not overlap arms or retry an ambiguous run: failed stages can leave an incoming PHP file, hidden clone, lock, or swapped cache. Use a fresh fixture for each root test:

root@kitploit:~
./scripts/reset-lab.sh --yes-delete-lab-volume
./scripts/mint-lab-auth.py [email protected]

Repeatability and failure handling

One successful corruption is supported per clamd process. A second invocation is defensible only after a fresh run's destructive recycle probe returns the expected scanner-failure response and EICAR proves recovery through the active socket. Do not reuse a run ID, staged chunk name, result bearer, or client work directory.

Stop immediately on any redirect, network timeout, ambiguous body/status, unexpected clean response, or failed EICAR recovery. Never manually resubmit the prior corruption request to an uncertain daemon.

The packaged service has Restart=no. Repeatability comes from the still active clamav-daemon.socket, not from an automatic service restart policy. If the socket is disabled or start-limited, deliberate crashes can leave the scanner unavailable.

Residue and cleanup

Expected target residue includes access/error logs, clamd crash records or cores, bounded orphan claim files from clean oracle guards, one append-only /var/zendto/incoming/Bridge<nonce>.1 DSO, and short-lived AuthData rows. Stock cleanup ages out ordinary orphan claims only after more than one day.

The root variant additionally leaves a nonce PHP armer, hidden clone and lock, the root-owned public JSON result, and private /run stdout/stderr. A successful wrapper restores genuine templates_c before executing the root command, but these evidence files remain until the container and volume are reset.

Revoke the lab upload bearer after testing:

root@kitploit:~
./scripts/revoke-lab-auth.py

Stop the target while preserving its evidence volume:

root@kitploit:~
docker compose down

For a clean fresh fixture, explicitly delete only this project's named volume and restart it from the pinned image:

root@kitploit:~
./scripts/reset-lab.sh --yes-delete-lab-volume

Archive the work directory and fingerprint JSON before resetting if they are needed as evidence.

Directory map

root@kitploit:~
docker-compose.yml                  exact systemd/socket target topology
PROFILE.json                        exact package/hash/CVD profile
image/*.tar.zst                     local ignored target artifact
poc/                                no-account driver and support modules
third_party/                        minimized builder/source/binary closure
scripts/setup.sh                    image/client/Compose setup
scripts/verify-bundle.sh            delivery-manifest verification
scripts/verify-target.sh            fail-closed target verification
scripts/verify-privesc-target.sh    root-variant semantic preflight
scripts/mint-lab-auth.py            local completed-email substitute
scripts/revoke-lab-auth.py          remove that lab bearer
scripts/reset-lab.sh                explicit clean-volume reset
source-recipe/                      installer provenance, not exact rebuild
docs/                               technical regression/audit notes

source-recipe/install.ZendTo.tgz and its runbook are included for provenance and source construction. Do not treat a new live-repository build as exact: the installer does not pin all packages or ClamAV databases. Exact regression requires the ignored local image archive to match PROFILE.json.

MANIFEST.sha256 covers every delivered file except itself. Docker image archives, the host-compiled third_party/installer-runtime/clamav-pespin/ pespin_summit helper, and generated .venv, .lab, work, and __pycache__ trees are ignored. Setup rebuilds the helper from its manifested C source and verifies the local image separately.

The packaging-time static, remote preflight, probabilistic resampling, and instrumented end-to-end checks are recorded in docs/BUNDLE_VALIDATION.md. The record distinguishes remote observations from the one validation run that was explicitly supplied a locally observed module base.

Download Tool
ComponentExact tested value
Docker image IDsha256:de6d2f06ca04943362a9bdec5026e808448953728f31bd926343a4ed2ca2ef7c
ZendTo6.15-8
ClamAV/libclamav package1.4.3+dfsg-1~deb12u2
libclamavlibclamav.so.12.0.3, SHA-256 55e3cd94…027c
libclamav Build IDe6427ab62146ee3001fe463d12e797e9d25bf81a
glibc2.36-9+deb12u14, SHA-256 6b4a4535…421
main databasev63, SHA-256 0b2182d2…365
daily databasev28082, SHA-256 cddbcccf…906
bytecode databasev339, SHA-256 6d4aa01f…ffb
clamdMaxThreads 12, IdleTimeout 30, Restart=no
lifecycleenabled clamav-daemon.socket, systemd PID 1
web runtimeApache 2.4.68, PHP 8.2.32
MAC postureunconfined privileged Docker fixture
root bridgestock clamav in www-data; /var/zendto root:www-data 0775
root consumerstock root cleanup cron, hourly at minute 25
Smarty cachedeterministic Smarty 4.5.4 compiled zendto.conf
exchange filesystemext4-backed named /var/zendto volume