
Reproduce la RCE no autenticada de ClamAV en ZendTo y la escalada de privilegios a root en un laboratorio autorizado, con destino Docker fijado, verificación con cierre ante fallos, salida vinculada a nonce y limpieza.
Este directorio es un paquete de reproducción independiente, para laboratorio autorizado, de la cadena de ejecución de código de ZendTo a ClamAV sin cuenta y de su continuación separada mediante Smarty/cron root con el perfil predeterminado. Incluye:
El PoC inicial ejecuta un comando seleccionado por el llamante como la cuenta de servicio estándar clamav. La variante opcional continúa ese punto de apoyo sin cuenta hasta un comando seleccionado por el llamante como root. Ambas usan id por defecto, rechazan la ruta literal /root/flag y devuelven la salida vinculada a un nonce a través de HTTPS.
Las relaciones vulnerables de grupo, directorio, Smarty y cron root son las predeterminadas del paquete/instalador Debian de ZendTo. No obstante, la alcanzabilidad de root depende del entorno: este fixture positivo exacto usa un volumen /var/zendto con ext4, FFI/POSIX/exec de PHP CLI funcionales y un proceso clamd sin confinamiento. La aplicación de AppArmor/SELinux o de semánticas de sistema de archivos incompatibles puede bloquear la continuación en otra instalación por lo demás estándar.
Use esto solo en el fixture desechable incluido o en otro sistema que esté explícitamente autorizado a probar.
El objetivo exacto y autoritativo es un artefacto local, intencionadamente sin versionar, en:
image/zendto-installer-systemd-debian12.tar.zst
Git ignora los archivos de imagen de Docker. Antes de una regresión exacta, coloque una copia local autorizada en esa ruta; scripts/setup.sh requiere su SHA-256 e ID de imagen registrados. source-recipe/README.md documenta cómo se construyó y capturó la imagen derivada del instalador. Una nueva construcción desde repositorios de paquetes en vivo es útil para probar la topología, pero no se presupone byte-idéntica al objetivo fijado.
El script de configuración y el entrypoint del contenedor rechazan cualquier discrepancia con el siguiente perfil:
Los valores completos están en PROFILE.json.
Esto es más sólido que fijar solo la versión del paquete ClamAV. La construcción del asignador también depende de glibc, del tráfico del parser dirigido por CVD, de los ajustes de los workers y de la biblioteca objetivo exacta. FreshClam está deshabilitado en la imagen capturada, y el arranque falla si daily.cvd se ha desviado o si ha aparecido un daily.cld.
El contenedor de investigación actualmente de larga duración en el espacio de trabajo padre no es el fixture limpio: las pruebas posteriores deshabilitaron su formulario de remitente externo y actualizaron su base de datos diaria. Esos cambios están excluidos intencionadamente aquí. Este paquete usa la línea base limpia contra la que se demostró la ejecución nativa.
/sys/fs/cgroupzstd, Python 3 con venv, un compilador de C, file y GNU readelfEl objetivo comparte el kernel del host y la implementación de ASLR. El comportamiento de mapeo dependiente del kernel sigue siendo, por tanto, una variable de portabilidad.
Solo un fixture systemd debe compartir el espacio de nombres de cgroup del host a la vez. En la máquina de investigación original, detenga el fixture antiguo sin eliminarlo:
docker stop zendto-installer-systemd-native
Más tarde puede restaurarse con docker start zendto-installer-systemd-native.
Desde este directorio:
./scripts/verify-bundle.sh
./scripts/setup.sh
El script de configuración:
.venv con las dependencias Python fijadas del PoC;/var/zendto e inicia systemd; yrenameat2(RENAME_EXCHANGE) ejecutada como clamav.Los endpoints predeterminados son solo de bucle local:
http://127.0.0.1:18084/
https://127.0.0.1:18447/
El certificado es autofirmado. El PoC usa deliberadamente verify=False en cada petición HTTP. Se pueden seleccionar otros puertos de bucle local antes de la configuración:
export ZENDTO_HTTP_PORT=19084
export ZENDTO_HTTPS_PORT=19447
./scripts/setup.sh
Use las mismas variables de entorno para los comandos posteriores de Compose/asistentes.
Compruebe el objetivo en cualquier momento:
./scripts/verify-target.sh
docker compose ps
La línea base limpia del instalador tiene:
allowExternalUploads = TRUE
confirmExternalEmails = TRUE
captcha = google
Sus valores de CAPTCHA y correo son marcadores de posición del instalador, por lo que el archivo intacto no puede entregar realmente el correo público de verificación. Las pruebas de regresión nativas representaron solo ese paso de aplicación completado con una fila AuthData equivalente de remitente externo. El código objetivo, los paquetes, el escáner, los permisos y la ruta de explotación no fueron parcheados.
Cree esa única fila de laboratorio y un archivo de token con modo 0600 con:
./scripts/mint-lab-auth.py [email protected]
El valor del token se suprime y se almacena en:
.lab/upload-auth-token.txt
Este asistente local no es una afirmación de que una implementación con subida externa deshabilitada pueda explotarse de forma remota. Si el formulario público de remitente externo está deshabilitado, el PoC se detiene correctamente antes de ClamAV incluso cuando se proporciona un archivo de token.
En una implementación autorizada configurada normalmente, omita --external-auth-token-file: abra manualmente la página de verificación, resuelva el CAPTCHA, reciba el mensaje generado por el objetivo en un buzón controlado por el atacante y pegue su URL/token en el prompt oculto.
Este modo no envía CAPTCHA, correo, capability, subida, POST de login ni contenido al escáner. --skip-clamd-tcp-probe también suprime la comprobación nativa opcional de solo lectura TCP/3310:
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
La enumeración sigue produciendo registros normales de acceso web y puede inicializar cachés normales de la aplicación. Su resultado de diseño Apache Debian/Ubuntu no acredita el paquete nativo, el Build ID, la libc, los CVD, la política de sockets ni el estado MAC. El script local de verificación Docker aporta esa evidencia para este fixture.
Esto valida el bearer de laboratorio y la política de destinatarios, pero no envía ningún archivo ni contenido al escáner:
.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]. coincide con el valor deliberadamente inusual de dominio interno test. introducido al crear este fixture de instalador. Una implementación real personalizada puede requerir un destinatario autorizado real.
Advertencia: esto no es una comprobación benigna de escáner. Antes del primer intento, el modo remoto envía EICAR, provoca deliberadamente el fallo de clamd con el discriminador PESpin, demuestra la recuperación activada por socket y ejecuta dos sondas terminales de página de códigos con ciclos adicionales de fallo/recuperación. Los mapeos rechazados matan clamd intencionadamente.
Use un directorio de trabajo nuevo y un comando inofensivo:
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
La ruta condicionada preferida comprueba el mapeo útil de nibble-8 antes de enviar un único portador de corrupción. El agregado actual de tres diseños es una corrección posterior del ciclo de vida; sus etapas históricas de oráculo de un solo diseño y RCE en el mismo proceso están documentadas por separado en docs/.
El fallback ciego de nibble-6 fijo está disponible explícitamente:
.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
Bajo un modelo uniforme independiente de nibbles, 47 mapeos nuevos son aproximadamente el 95% y 72 aproximadamente el 99%. Son probabilidades geométricas de mapeo, no garantías de fiabilidad de extremo a extremo. Cada mapeo rechazado tarda al menos el retardo de recuperación de 12 segundos más el período obligatorio de retiro del worker de 40 segundos, sin contar el tiempo de escaneo. Una ejecución de 72 intentos puede superar una hora.
El resultado no se lee de un callback ni de una webshell. El DSO crea un bearer AuthData separado de solo salida con validez de 30 minutos, ejecuta /bin/sh -c como clamav y devuelve stdout/stderr acotados a través de una página anónima de ZendTo. El éxito requiere un resultado enmarcado con todo lo siguiente consistente:
zendto_unauth_clamav_rce_result=1Una prueba faltante, duplicada, truncada o discrepante es una ejecución fallida.
Ejecute esto solo después de que ./scripts/verify-target.sh informe éxito tanto en [target] como en [privesc-target]. El wrapper primero realiza la misma etapa ClamAV sin cuenta, exige su prueba exacta de armado acotado y luego espera al consumidor root cron estándar sin cambios:
.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
La opción de archivo de token es solo el sustituto del correo completado en este fixture. En un flujo público autorizado, omítala y complete manualmente el prompt de CAPTCHA/correo. chain-unauth no usa ningún nombre de usuario ni contraseña de ZendTo.
La etapa root está deliberadamente separada aunque el wrapper automatiza su traspaso. Esta:
savechunk.php no autenticado;clamav;/zendto-root-result-<nonce>.json con verify=False, exigiendo euid=0, el hash exacto del comando, el marcador de restauración y el nonce de finalización.Cron puede hacer que la última consulta espere casi una hora; el timeout predeterminado es de 3900 segundos. El comando root está limitado a 1024 bytes y 30 segundos, con un máximo de 1 MiB para stdout y 1 MiB para stderr. Su JSON direccionado por nonce es intencionadamente legible por web en este laboratorio desechable, así que use solo salida de prueba inofensiva.
Para orquestación manual, emit imprime el comando exacto que debe pasarse a --command del PoC sin cuenta; después de la prueba de armado, use poll con el ID de ejecución impreso y el SHA-256 del comando root exacto. Consulte docs/SMARTY_ROOT_BRIDGE.md para la construcción y la transcripción de auditoría.
Se espera que esta variante se detenga de forma segura cuando falta un prerequisito. No superponga brazos ni reintente una ejecución ambigua: las etapas fallidas pueden dejar un archivo PHP entrante, un clon oculto, un bloqueo o una caché intercambiada. Use un fixture nuevo para cada prueba root:
./scripts/reset-lab.sh --yes-delete-lab-volume
./scripts/mint-lab-auth.py [email protected]
Se admite una corrupción exitosa por proceso clamd. Una segunda invocación solo es defendible después de que la sonda destructiva de reciclaje de una ejecución nueva devuelva la respuesta esperada de fallo del escáner y EICAR demuestre la recuperación a través del socket activo. No reutilice un ID de ejecución, un nombre de chunk escalonado, un bearer de resultado ni un directorio de trabajo del cliente.
Deténgase inmediatamente ante cualquier redirección, timeout de red, cuerpo/estado ambiguo, respuesta limpia inesperada o fallo de recuperación EICAR. Nunca reenvíe manualmente la petición de corrupción anterior a un daemon incierto.
El servicio empaquetado tiene Restart=no. La repetibilidad proviene del clamav-daemon.socket aún activo, no de una política automática de reinicio del servicio. Si el socket está deshabilitado o con límite de arranques, los fallos deliberados pueden dejar el escáner no disponible.
Los residuos esperados del objetivo incluyen registros de acceso/error, registros o núcleos de fallo de clamd, archivos de reclamación huérfanos acotados de los guards de oráculo limpios, un DSO append-only /var/zendto/incoming/Bridge<nonce>.1 y filas AuthData de corta duración. La limpieza estándar elimina las reclamaciones huérfanas ordinarias solo después de más de un día.
La variante root deja además un armer PHP con nonce, un clon y bloqueo ocultos, el resultado JSON público propiedad de root y stdout/stderr privados en /run. Un wrapper exitoso restaura el templates_c genuino antes de ejecutar el comando root, pero estos archivos de evidencia permanecen hasta que se restablezcan el contenedor y el volumen.
Revoque el bearer de subida de laboratorio después de las pruebas:
./scripts/revoke-lab-auth.py
Detenga el objetivo conservando su volumen de evidencia:
docker compose down
Para un fixture nuevo limpio, elimine explícitamente solo el volumen nombrado de este proyecto y reinícielo desde la imagen fijada:
./scripts/reset-lab.sh --yes-delete-lab-volume
Archive el directorio de trabajo y el JSON de fingerprint antes de restablecer si se necesitan como evidencia.
docker-compose.yml topología exacta del objetivo systemd/socket
PROFILE.json perfil exacto de paquete/hash/CVD
image/*.tar.zst artefacto objetivo local ignorado
poc/ driver sin cuenta y módulos de soporte
third_party/ cierre minimizado de builder/fuente/binario
scripts/setup.sh configuración de imagen/cliente/Compose
scripts/verify-bundle.sh verificación del manifiesto de entrega
scripts/verify-target.sh verificación del objetivo a prueba de fallos
scripts/verify-privesc-target.sh preflight semántico de la variante root
scripts/mint-lab-auth.py sustituto local de correo completado
scripts/revoke-lab-auth.py elimina ese bearer de laboratorio
scripts/reset-lab.sh restablecimiento explícito de volumen limpio
source-recipe/ procedencia del instalador, no reconstrucción exacta
docs/ notas técnicas de regresión/auditoría
source-recipe/install.ZendTo.tgz y su runbook se incluyen para procedencia y construcción de la fuente. No trate una construcción nueva desde repositorios en vivo como exacta: el instalador no fija todos los paquetes ni las bases de datos de ClamAV. La regresión exacta requiere que el archivo de imagen local ignorado coincida con PROFILE.json.
MANIFEST.sha256 cubre todos los archivos entregados excepto sí mismo. Los archivos de imagen Docker, el helper third_party/installer-runtime/clamav-pespin/pespin_summit compilado en el host, y los árboles generados .venv, .lab, work y __pycache__ están ignorados. El setup reconstruye el helper desde su fuente C manifestada y verifica la imagen local por separado.
Las comprobaciones estáticas en tiempo de empaquetado, el preflight remoto, el remuestreo probabilístico y las comprobaciones instrumentadas de extremo a extremo están registradas en docs/BUNDLE_VALIDATION.md. El registro distingue las observaciones remotas de la única ejecución de validación a la que se suministró explícitamente una base de módulo observada localmente.
| Componente | Valor exacto probado |
|---|
| ID de imagen Docker | sha256:de6d2f06ca04943362a9bdec5026e808448953728f31bd926343a4ed2ca2ef7c |
| ZendTo | 6.15-8 |
| Paquete ClamAV/libclamav | 1.4.3+dfsg-1~deb12u2 |
| libclamav | libclamav.so.12.0.3, SHA-256 55e3cd94…027c |
| Build ID de libclamav | e6427ab62146ee3001fe463d12e797e9d25bf81a |
| glibc | 2.36-9+deb12u14, SHA-256 6b4a4535…421 |
| base de datos principal | v63, SHA-256 0b2182d2…365 |
| base de datos diaria | v28082, SHA-256 cddbcccf…906 |
| base de datos bytecode | v339, SHA-256 6d4aa01f…ffb |
| clamd | MaxThreads 12, IdleTimeout 30, Restart=no |
| ciclo de vida | clamav-daemon.socket habilitado, systemd como PID 1 |
| runtime web | Apache 2.4.68, PHP 8.2.32 |
| postura MAC | fixture Docker privilegiado sin confinamiento |
| puente a root | clamav estándar en www-data; /var/zendto root:www-data 0775 |
| consumidor root | cron de limpieza root estándar, cada hora en el minuto 25 |
| caché Smarty | zendto.conf compilado determinista con Smarty 4.5.4 |
| sistema de archivos de intercambio | volumen nombrado /var/zendto con ext4 |