
Riproduce la RCE non autenticata di ZendTo tramite ClamAV e l'escalation dei privilegi di root in un laboratorio autorizzato, con target Docker fissato, verifica fail-closed, output vincolato a nonce e pulizia.
Questa directory è un bundle autonomo di riproduzione per laboratorio autorizzato della catena di esecuzione di codice senza account da ZendTo a ClamAV e della sua continuazione separata Smarty/cron-root con profilo predefinito. Include:
Il PoC iniziale esegue un comando scelto dal chiamante con l'account di servizio standard clamav. La variante opzionale estende quel punto d'appoggio senza account fino a un comando scelto dal chiamante come root. Entrambi usano id come predefinito, rifiutano il percorso letterale /root/flag e restituiscono output vincolato a un nonce su HTTPS.
Le relazioni vulnerabili tra gruppo, directory, Smarty e cron-root sono i valori predefiniti del pacchetto/installer Debian di ZendTo. La raggiungibilità di root è comunque condizionata dall'ambiente: questo esatto fixture positivo usa un volume /var/zendto su ext4, PHP CLI funzionante con FFI/POSIX/exec e un processo clamd senza confinamento. L'applicazione di AppArmor/SELinux o di semantiche del filesystem incompatibili può bloccare la continuazione su un'altra installazione altrimenti standard.
Usalo solo sul fixture usa-e-getta incluso o su un altro sistema che sei esplicitamente autorizzato a testare.
Il target esatto di riferimento è un artefatto locale, volutamente senza versione, al percorso:
image/zendto-installer-systemd-debian12.tar.zst
Git ignora gli archivi delle immagini Docker. Prima della regressione esatta, inserisci una copia locale autorizzata in quel percorso; scripts/setup.sh richiede lo SHA-256 e l'ID immagine registrati. source-recipe/README.md documenta come l'immagine derivata dall'installer è stata costruita e acquisita. Una nuova build dai repository di pacchetti live è utile per testare la topologia, ma non è considerata byte-identica al target fissato.
Lo script di setup e l'entrypoint del container rifiutano qualsiasi discrepanza rispetto al seguente profilo:
I valori completi sono in PROFILE.json.
Questo è un vincolo più forte del fissare solo la versione del pacchetto ClamAV. La costruzione dell'allocator dipende anche da glibc, dal traffico del parser guidato dai CVD, dalle impostazioni dei worker e dalla libreria target esatta. FreshClam è disabilitato nell'immagine acquisita e l'avvio fallisce se daily.cvd è cambiato o se è comparso un daily.cld.
Il container di ricerca attualmente in esecuzione da molto tempo nel workspace padre non è il fixture pulito: i test successivi hanno disabilitato il suo modulo per i mittenti esterni e aggiornato il suo database daily. Queste modifiche sono volutamente escluse qui. Questo bundle usa la baseline pulita contro cui è stata dimostrata l'esecuzione nativa.
/sys/fs/cgroupzstd, Python 3 con venv, un compilatore C, file e GNU readelfIl target condivide il kernel dell'host e l'implementazione di ASLR. Il comportamento dei mapping dipendente dal kernel rimane quindi una variabile di portabilità.
Un solo fixture systemd dovrebbe condividere il namespace cgroup dell'host alla volta. Sulla macchina di ricerca originale, ferma il fixture più vecchio senza eliminarlo:
docker stop zendto-installer-systemd-native
Può essere ripristinato in seguito con docker start zendto-installer-systemd-native.
Da questa directory:
./scripts/verify-bundle.sh
./scripts/setup.sh
Lo script di setup:
.venv con le dipendenze Python del PoC fissate;/var/zendto e avvia systemd; erenameat2(RENAME_EXCHANGE) eseguita come clamav.Gli endpoint predefiniti sono solo loopback:
http://127.0.0.1:18084/
https://127.0.0.1:18447/
Il certificato è autofirmato. Il PoC usa deliberatamente verify=False per ogni richiesta HTTP. È possibile selezionare porte loopback diverse prima del setup:
export ZENDTO_HTTP_PORT=19084
export ZENDTO_HTTPS_PORT=19447
./scripts/setup.sh
Usa le stesse variabili d'ambiente per i successivi comandi Compose/helper.
Controlla il target in qualsiasi momento:
./scripts/verify-target.sh
docker compose ps
La baseline pulita dell'installer ha:
allowExternalUploads = TRUE
confirmExternalEmails = TRUE
captcha = google
I suoi valori CAPTCHA e mail sono segnaposto dell'installer, quindi l'archivio non modificato non può realmente consegnare l'email di verifica pubblica. Il test di regressione nativo ha rappresentato solo quel passaggio applicativo completato con una riga AuthData equivalente per mittente esterno. Il codice target, i pacchetti, lo scanner, i permessi e il percorso di exploit non sono stati patchati.
Crea quella singola riga di laboratorio e un file token con modalità 0600 con:
./scripts/mint-lab-auth.py [email protected]
Il valore del token è soppresso e salvato in:
.lab/upload-auth-token.txt
Questo helper locale non è un'affermazione che una distribuzione con upload esterni disabilitati possa essere sfruttata da remoto. Se il modulo pubblico per mittenti esterni è disabilitato, il PoC si ferma correttamente prima di ClamAV anche quando viene fornito un file token.
Su una distribuzione autorizzata configurata normalmente, ometti --external-auth-token-file: apri manualmente la pagina di verifica, risolvi il CAPTCHA, ricevi il messaggio generato dal target su una casella di posta controllata dall'attaccante e incolla il suo URL/token al prompt nascosto.
Questa modalità non invia CAPTCHA, email, capability, upload, POST di login o contenuti allo scanner. --skip-clamd-tcp-probe sopprime anche il controllo opzionale di sola lettura TCP/3310 nativo:
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
L'enumerazione produce comunque normali log di accesso web e può inizializzare le cache ordinarie dell'applicazione. Il suo risultato con layout Apache Debian/Ubuntu non attesta il pacchetto nativo, il Build ID, la libc, i CVD, la politica del socket o lo stato MAC. Lo script locale di verifica Docker fornisce questa evidenza per il fixture.
Questo valida il bearer di laboratorio e la politica sui destinatari, ma non invia alcun file o contenuto allo scanner:
.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]. corrisponde al valore volutamente insolito del dominio interno test. inserito quando è stato creato questo fixture dell'installer. Una distribuzione reale personalizzata può richiedere un destinatario effettivamente autorizzato.
Attenzione: questo non è un controllo scanner benigno. Prima del primo tentativo, la modalità remota invia EICAR, fa crashare deliberatamente clamd con il discriminatore PESpin, dimostra il recupero tramite socket e esegue due sonde di code-page terminali con ulteriori cicli di crash/recupero. I mapping rifiutati uccidono intenzionalmente clamd.
Usa una directory di lavoro nuova e un comando innocuo:
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
Il percorso condizionato preferito verifica l'utile mapping nibble-8 prima di inviare un singolo vettore di corruzione. L'attuale aggregato a tre layout è una correzione successiva del ciclo di vita; le sue fasi storiche di oracle a un layout e RCE nello stesso processo sono documentate separatamente in docs/.
Il fallback cieco a nibble-6 fisso è disponibile esplicitamente:
.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
Con un modello uniforme e indipendente dei nibble, 47 mapping nuovi danno circa il 95% e 72 circa il 99%. Queste sono probabilità geometriche dei mapping, non garanzie di affidabilità end-to-end. Ogni mapping rifiutato richiede almeno il ritardo di recupero di 12 secondi più il periodo obbligatorio di ritiro del worker di 40 secondi, escluso il tempo di scansione. Un run con 72 tentativi può superare un'ora.
Il risultato non viene letto da un callback o da una webshell. La DSO crea un bearer AuthData separato di sola output della durata di 30 minuti, esegue /bin/sh -c come clamav e restituisce stdout/stderr limitati attraverso una pagina ZendTo anonima. Il successo richiede un singolo risultato delimitato con tutti i seguenti elementi coerenti:
zendto_unauth_clamav_rce_result=1Una prova mancante, duplicata, troncata o non corrispondente è un run fallito.
Esegui questo solo dopo che ./scripts/verify-target.sh riporta il successo sia di [target] che di [privesc-target]. Il wrapper esegue prima la stessa fase ClamAV senza account, richiede la sua esatta prova di arming delimitata, quindi attende l'invariato consumer cron di root standard:
.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
L'opzione del file token è solo il sostituto dell'email completata del fixture. In un flusso di lavoro pubblico autorizzato, omettila e completa manualmente il prompt CAPTCHA/email. chain-unauth non usa alcun nome utente o password di ZendTo.
La fase root è volutamente separata anche se il wrapper ne automatizza il passaggio di consegna. Essa:
savechunk.php non autenticato;clamav;/zendto-root-result-<nonce>.json con verify=False, richiedendo euid=0, l'hash esatto del comando, il marcatore di ripristino e il nonce di completamento.Cron può far aspettare quasi un'ora per il polling finale; il timeout predefinito è di 3900 secondi. Il comando root è limitato a 1024 byte e 30 secondi, con al massimo 1 MiB ciascuno per stdout e stderr. Il suo JSON indirizzato tramite nonce è volutamente leggibile dal web in questo laboratorio usa-e-getta, quindi usa solo output di test innocui.
Per l'orchestrazione manuale, emit stampa il comando esatto da passare a --command del PoC senza account; dopo la prova di arming, usa poll con l'ID run stampato e lo SHA-256 del comando root esatto. Vedi docs/SMARTY_ROOT_BRIDGE.md per la costruzione e il verbale di audit.
Ci si aspetta che questa variante si fermi in sicurezza quando manca un prerequisito. Non sovrapporre gli arm né ritentare un run ambiguo: le fasi fallite possono lasciare un file PHP in arrivo, un clone nascosto, un lock o una cache scambiata. Usa un fixture nuovo per ogni test root:
./scripts/reset-lab.sh --yes-delete-lab-volume
./scripts/mint-lab-auth.py [email protected]
Per ogni processo clamd è supportata una singola corruzione riuscita. Una seconda invocazione è difendibile solo dopo che la sonda distruttiva di riciclo di un run nuovo restituisce la risposta di errore scanner attesa e EICAR dimostra il recupero attraverso il socket attivo. Non riutilizzare un ID run, un nome di chunk in stage, un bearer di risultato o una directory di lavoro client.
Fermati immediatamente su qualsiasi redirect, timeout di rete, body/status ambiguo, risposta pulita inattesa o recupero EICAR fallito. Non reinviare mai manualmente la precedente richiesta di corruzione a un demone in stato incerto.
Il servizio pacchettizzato ha Restart=no. La ripetibilità deriva dal clamav-daemon.socket ancora attivo, non da una politica di riavvio automatico del servizio. Se il socket è disabilitato o limitato negli avvii, i crash deliberati possono lasciare lo scanner non disponibile.
I residui attesi sul target includono log di accesso/errore, record di crash o core di clamd, file di claim orfani limitati dei guard oracle puliti, una DSO /var/zendto/incoming/Bridge<nonce>.1 in sola append e righe AuthData di breve durata. La pulizia standard elimina i normali claim orfani solo dopo più di un giorno.
La variante root lascia inoltre un armer PHP con nonce, clone e lock nascosti, il risultato JSON pubblico di proprietà di root e stdout/stderr privati in /run. Un wrapper riuscito ripristina la templates_c genuina prima di eseguire il comando root, ma questi file di evidenza rimangono finché il container e il volume non vengono resettati.
Revoca il bearer di upload di laboratorio dopo il test:
./scripts/revoke-lab-auth.py
Ferma il target preservando il suo volume di evidenza:
docker compose down
Per un fixture nuovo e pulito, elimina esplicitamente solo il volume nominato di questo progetto e riavvialo dall'immagine fissata:
./scripts/reset-lab.sh --yes-delete-lab-volume
Archivia la directory di lavoro e il JSON del fingerprint prima del reset se servono come evidenza.
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 e il suo runbook sono inclusi per la provenienza e la costruzione delle sorgenti. Non trattare una nuova build da repository live come esatta: l'installer non fissa tutti i pacchetti o i database ClamAV. La regressione esatta richiede che l'archivio immagine locale ignorato corrisponda a PROFILE.json.
MANIFEST.sha256 copre ogni file consegnato tranne sé stesso. Gli archivi delle immagini Docker, l'helper third_party/installer-runtime/clamav-pespin/pespin_summit compilato sull'host e gli alberi generati .venv, .lab, work e __pycache__ sono ignorati. Il setup ricompila l'helper dalla sua sorgente C manifestata e verifica separatamente l'immagine locale.
I controlli statici al momento del packaging, il preflight remoto, il ricampionamento probabilistico e i controlli end-to-end strumentati sono registrati in docs/BUNDLE_VALIDATION.md. Il record distingue le osservazioni remote dall'unico run di validazione a cui è stata esplicitamente fornita una base di modulo osservata localmente.
| Componente | Valore esatto testato |
|---|
| Docker image ID | sha256:de6d2f06ca04943362a9bdec5026e808448953728f31bd926343a4ed2ca2ef7c |
| ZendTo | 6.15-8 |
| ClamAV/libclamav package | 1.4.3+dfsg-1~deb12u2 |
| libclamav | libclamav.so.12.0.3, SHA-256 55e3cd94…027c |
| libclamav Build ID | e6427ab62146ee3001fe463d12e797e9d25bf81a |
| glibc | 2.36-9+deb12u14, SHA-256 6b4a4535…421 |
| main database | v63, SHA-256 0b2182d2…365 |
| daily database | v28082, SHA-256 cddbcccf…906 |
| bytecode database | v339, SHA-256 6d4aa01f…ffb |
| clamd | MaxThreads 12, IdleTimeout 30, Restart=no |
| ciclo di vita | clamav-daemon.socket abilitato, systemd PID 1 |
| web runtime | Apache 2.4.68, PHP 8.2.32 |
| postura MAC | fixture Docker privilegiato e non confinato |
| ponte root | clamav standard in www-data; /var/zendto root:www-data 0775 |
| consumatore root | cron di pulizia root standard, ogni ora al minuto 25 |
| cache Smarty | zendto.conf compilato deterministico Smarty 4.5.4 |
| filesystem di scambio | volume nominato /var/zendto su ext4 |